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

package Ice;

public final class ObjectAdapter
{
    public String
    getName()
    {
	//
        // No mutex lock necessary, _name is immutable.
	//
        return _name;
    }

    public synchronized Communicator
    getCommunicator()
    {
	checkForDeactivation();
	
	return _communicator;
    }

    public void
    activate()
    {
	IceInternal.LocatorInfo locatorInfo = null;
	boolean printAdapterReady = false;

	synchronized(this)
	{
	    checkForDeactivation();
	    
	    if(!_printAdapterReadyDone)
	    {
		locatorInfo = _locatorInfo;
		printAdapterReady = _instance.properties().getPropertyAsInt("Ice.PrintAdapterReady") > 0;
		_printAdapterReadyDone = true;
	    }
	    
	    final int sz = _incomingConnectionFactories.size();
	    java.util.Enumeration e = _incomingConnectionFactories.elements();
	    while(e.hasMoreElements())
	    {
		IceInternal.IncomingConnectionFactory factory =
		    (IceInternal.IncomingConnectionFactory)e.nextElement();
		factory.activate();
	    }
	}

	if(_id.length() > 0)
	{
	    //
	    // We must get and call on the locator registry outside the thread
	    // synchronization to avoid deadlocks. (we can't make remote calls
	    // within the OA synchronization because the remote call will
	    // indirectly call isLocal() on this OA with the OA factory
	    // locked).
	    //

	    LocatorRegistryPrx locatorRegistry = null;
	    if(locatorInfo != null)
	    {
		//
		// TODO: This might throw if we can't connect to the
		// locator. Shall we raise a special exception for the
		// activate operation instead of a non obvious network
		// exception?
		//
		locatorRegistry = _locatorInfo.getLocatorRegistry();
	    }
	    
	    if(locatorRegistry != null)
	    {
		try
		{
		    Identity ident = new Identity();
		    ident.category = "";
		    ident.name = "dummy";
		    if(_replicaGroupId.length() == 0)
		    {
			locatorRegistry.setAdapterDirectProxy(_id, createDirectProxy(ident));
		    }
		    else
		    {
			locatorRegistry.setReplicatedAdapterDirectProxy(_id, _replicaGroupId,createDirectProxy(ident));
		    }
		}
		catch(ObjectAdapterDeactivatedException ex)
		{
		    // IGNORE: The object adapter is already inactive.
		}
		catch(AdapterNotFoundException ex)
		{
		    NotRegisteredException ex1 = new NotRegisteredException();
		    ex1.kindOfObject = "object adapter";
		    ex1.id = _id;
		    throw ex1;
		}
		catch(InvalidReplicaGroupIdException ex)
		{
		    NotRegisteredException ex1 = new NotRegisteredException();
		    ex1.kindOfObject = "replica group";
		    ex1.id = _replicaGroupId;
		    throw ex1;
		}
		catch(AdapterAlreadyActiveException ex)
		{
		    ObjectAdapterIdInUseException ex1 = new ObjectAdapterIdInUseException();
		    ex1.id = _id;
		    throw ex1;
		}
	    }
	}

	if(printAdapterReady)
	{
	    System.out.println(_name + " ready");
	}
    }

    public synchronized void
    hold()
    {
	checkForDeactivation();
	
	java.util.Enumeration e = _incomingConnectionFactories.elements();
	while(e.hasMoreElements())
	{
            IceInternal.IncomingConnectionFactory factory =
                (IceInternal.IncomingConnectionFactory)e.nextElement();
            factory.hold();
        }
    }

    public synchronized void
    waitForHold()
    {
	checkForDeactivation();
	
	java.util.Enumeration e = _incomingConnectionFactories.elements();
	while(e.hasMoreElements())
	{
	    IceInternal.IncomingConnectionFactory factory =
		(IceInternal.IncomingConnectionFactory)e.nextElement();
	    factory.waitUntilHolding();
	}
    } 

    public void
    deactivate()
    {
	java.util.Vector incomingConnectionFactories;
	IceInternal.OutgoingConnectionFactory outgoingConnectionFactory;

	synchronized(this)
	{
	    //
	    // Ignore deactivation requests if the object adapter has
	    // already been deactivated.
	    //
	    if(_deactivated)
	    {
		return;
	    }

	    
	    //
	    // No clone call with J2ME.
	    //
	    //incomingConnectionFactories = (java.util.Vector)_incomingConnectionFactories.clone();
	    incomingConnectionFactories = new java.util.Vector(_incomingConnectionFactories.size());
            java.util.Enumeration e = _incomingConnectionFactories.elements();
            while(e.hasMoreElements())
            {
                incomingConnectionFactories.addElement(e.nextElement());
            }
	    outgoingConnectionFactory = _instance.outgoingConnectionFactory();
	    
	    _deactivated = true;

	    notifyAll();
	}

	//
	// Must be called outside the thread synchronization, because
	// Connection::destroy() might block when sending a
	// CloseConnection message.
	//
	java.util.Enumeration e = incomingConnectionFactories.elements();
	while(e.hasMoreElements())
	{
	    IceInternal.IncomingConnectionFactory factory =
		(IceInternal.IncomingConnectionFactory)e.nextElement();
	    factory.destroy();
	}
	
	//
	// Must be called outside the thread synchronization, because
	// changing the object adapter might block if there are still
	// requests being dispatched.
	//
	outgoingConnectionFactory.removeAdapter(this);
    }

    public void
    waitForDeactivate()
    {
	synchronized(this)
	{
	    //
	    // First we wait for deactivation of the adapter itself, and
	    // for the return of all direct method calls using this
	    // adapter.
	    //
	    while(!_deactivated || _directCount > 0)
	    {
		try
		{
		    wait();
		}
		catch(InterruptedException ex)
		{
		}
	    }
	    
	    //
	    // If some other thread is currently deactivating, we wait
	    // until this thread is finished.
	    //
	    while(_waitForDeactivate)
	    {
		try
		{
		    wait();
		}
		catch(InterruptedException ex)
		{
		}
	    }
	    _waitForDeactivate = true;
	}
	
	//
	// Now we wait for until all incoming connection factories are
	// finished.
	//
	if(_incomingConnectionFactories != null)
	{
	    java.util.Enumeration e = _incomingConnectionFactories.elements();
	    while(e.hasMoreElements())
	    {
		IceInternal.IncomingConnectionFactory factory =
		    (IceInternal.IncomingConnectionFactory)e.nextElement();
		factory.waitUntilFinished();
	    }
	}
	
	//
	// Now it's also time to clean up our servants and servant
	// locators.
	//
	if(_servantManager != null)
	{
	    _servantManager.destroy();
	}
	
	synchronized(this)
	{
	    //
	    // Signal that waiting is complete.
	    //
	    _waitForDeactivate = false;
	    notifyAll();

	    //
	    // We're done, now we can throw away all incoming connection
	    // factories.
	    //
	    _incomingConnectionFactories = null;
	    
	    //
	    // Remove object references (some of them cyclic).
	    //
	    _instance = null;
	    _servantManager = null;
	    _communicator = null;
	}
    }

    public ObjectPrx
    add(Ice.Object object, Identity ident)
    {
        return addFacet(object, ident, "");
    }

    public synchronized ObjectPrx
    addFacet(Ice.Object object, Identity ident, String facet)
    {
	checkForDeactivation();
	checkIdentity(ident);
	
        //
        // Create a copy of the Identity argument, in case the caller
        // reuses it.
        //
        Identity id = new Identity();
        id.category = ident.category;
        id.name = ident.name;

	_servantManager.addServant(object, id, facet);

        return newProxy(id, facet);
    }

    public ObjectPrx
    addWithUUID(Ice.Object object)
    {
        return addFacetWithUUID(object, "");
    }

    public ObjectPrx
    addFacetWithUUID(Ice.Object object, String facet)
    {
        Identity ident = new Identity();
        ident.category = "";
        ident.name = Util.generateUUID();

        return addFacet(object, ident, facet);
    }

    public Ice.Object
    remove(Identity ident)
    {
        return removeFacet(ident, "");
    }

    public synchronized Ice.Object
    removeFacet(Identity ident, String facet)
    {
	checkForDeactivation();
        checkIdentity(ident);

	return _servantManager.removeServant(ident, facet);
    }

    public synchronized java.util.Hashtable
    removeAllFacets(Identity ident)
    {
	checkForDeactivation();
        checkIdentity(ident);

	return _servantManager.removeAllFacets(ident);
    }

    public Ice.Object
    find(Identity ident)
    {
        return findFacet(ident, "");
    }

    public synchronized Ice.Object
    findFacet(Identity ident, String facet)
    {
	checkForDeactivation();
        checkIdentity(ident);

        return _servantManager.findServant(ident, facet);
    }

    public synchronized java.util.Hashtable
    findAllFacets(Identity ident)
    {
	checkForDeactivation();
        checkIdentity(ident);

        return _servantManager.findAllFacets(ident);
    }

    public synchronized Ice.Object
    findByProxy(ObjectPrx proxy)
    {
	checkForDeactivation();

        IceInternal.Reference ref = ((ObjectPrxHelperBase)proxy).__reference();
        return findFacet(ref.getIdentity(), ref.getFacet());
    }

    public synchronized ObjectPrx
    createProxy(Identity ident)
    {
	checkForDeactivation();
        checkIdentity(ident);

        return newProxy(ident, "");
    }

    public synchronized ObjectPrx
    createDirectProxy(Identity ident)
    {
	checkForDeactivation();
        checkIdentity(ident);

        return newDirectProxy(ident, "");
    }

    public synchronized ObjectPrx
    createIndirectProxy(Identity ident)
    {
	checkForDeactivation();
        checkIdentity(ident);

        return newIndirectProxy(ident, "", _id);
    }

    public synchronized ObjectPrx
    createReverseProxy(Identity ident)
    {
	checkForDeactivation();
        checkIdentity(ident);

        //
        // Get all incoming connections for this object adapter.
        //
        java.util.Vector connections = new java.util.Vector();
        java.util.Enumeration e = _incomingConnectionFactories.elements();
	while(e.hasMoreElements())
        {
            IceInternal.IncomingConnectionFactory factory =
                (IceInternal.IncomingConnectionFactory)e.nextElement();
            Connection[] conns = factory.connections();
            for(int j = 0; j < conns.length; ++j)
            {
                connections.addElement(conns[j]);
            }
        }

        //
        // Create a reference and return a reverse proxy for this
        // reference.
        //
        IceInternal.Endpoint[] endpoints = new IceInternal.Endpoint[0];
        Connection[] arr = new Connection[connections.size()];
        connections.copyInto( arr);
        IceInternal.Reference ref = _instance.referenceFactory().create(ident, new java.util.Hashtable(), "",
                                                                        IceInternal.Reference.ModeTwoway, arr);
        return _instance.proxyFactory().referenceToProxy(ref);
    }

    public synchronized void
    addRouter(RouterPrx router)
    {
	checkForDeactivation();

        IceInternal.RouterInfo routerInfo = _instance.routerManager().get(router);
        if(routerInfo != null)
        {
	    _routerInfos.addElement(routerInfo);

            //
            // Add the router's server proxy endpoints to this object
            // adapter.
            //
            ObjectPrxHelperBase proxy = (ObjectPrxHelperBase)routerInfo.getServerProxy();
            IceInternal.Endpoint[] endpoints = proxy.__reference().getEndpoints();
            for(int i = 0; i < endpoints.length; ++i)
            {
                _routerEndpoints.addElement(endpoints[i]);
            }
	    
            IceUtil.Arrays.sort(_routerEndpoints); // Must be sorted.
	    //
	    // Remove duplicate endpoints, so we have a list of unique
	    // endpoints.
	    //
	    for(int i = 0; i < _routerEndpoints.size()-1; )
	    {
                java.lang.Object o1 = _routerEndpoints.elementAt(i);
                java.lang.Object o2 = _routerEndpoints.elementAt(i + 1);
                if(o1.equals(o2))
                {
                    _routerEndpoints.removeElementAt(i);
                }
		else
		{
		    ++i;
		}
	    }

            //
            // Associate this object adapter with the router. This way,
            // new outgoing connections to the router's client proxy will
            // use this object adapter for callbacks.
            //
            routerInfo.setAdapter(this);

            //
            // Also modify all existing outgoing connections to the
            // router's client proxy to use this object adapter for
            // callbacks.
            //      
            _instance.outgoingConnectionFactory().setRouterInfo(routerInfo);
        }
    }

    public synchronized void
    removeRouter(RouterPrx router)
    {
	checkForDeactivation();

	IceInternal.RouterInfo routerInfo = _instance.routerManager().erase(router);
	if(routerInfo != null)
	{
	    //
	    // Rebuild the router endpoints from our set of router infos.
	    //
	    _routerEndpoints.removeAllElements();
	    for(int p = 0; p < _routerInfos.size();)
	    {
		IceInternal.RouterInfo curr = (IceInternal.RouterInfo)_routerInfos.elementAt(p);
		if(curr == routerInfo)
		{
		    _routerEndpoints.removeElementAt(p);
		    continue;
		}
		ObjectPrxHelperBase proxy = (ObjectPrxHelperBase)routerInfo.getServerProxy();
		IceInternal.Endpoint[] endpoints = proxy.__reference().getEndpoints();
		for(int i = 0; i < endpoints.length; ++i)
		{
		    _routerEndpoints.addElement(endpoints[i]);
		}
		++p;
	    }

            IceUtil.Arrays.sort(_routerEndpoints); // Must be sorted.
	    //
	    // Remove duplicate endpoints, so we have a list of unique
	    // endpoints.
	    //
            for(int i = 0; i < _routerEndpoints.size() - 1; ++i)
            {
                java.lang.Object o1 = _routerEndpoints.elementAt(i);
                java.lang.Object o2 = _routerEndpoints.elementAt(i + 1);
                if(o1.equals(o2))
                {
                    _routerEndpoints.removeElementAt(i);
                }
            }

	    //
	    // Clear this object adapter with the router.
	    //
	    routerInfo.setAdapter(null);

	    //
	    // Also modify all existing outgoing connections to the
	    // router's client proxy to use this object adapter for
	    // callbacks.
	    //	
	    _instance.outgoingConnectionFactory().setRouterInfo(routerInfo);
	}
    }

    public synchronized void
    setLocator(LocatorPrx locator)
    {
	checkForDeactivation();

	_locatorInfo = _instance.locatorManager().get(locator);
    }

    public synchronized LocatorPrx
    getLocator()
    {
        checkForDeactivation();

        LocatorPrx locator = null;

        if(_locatorInfo != null)
        {
            locator = _locatorInfo.getLocator();
        }

        return locator;
    }

    public void
    flushBatchRequests()
    {
	java.util.Vector f;
	synchronized(this)
	{
	    //
	    // No clone() call with J2ME.
	    //
	    //f = (java.util.Vector)_incomingConnectionFactories.clone();
	    f = new java.util.Vector(_incomingConnectionFactories.size());
            java.util.Enumeration e = _incomingConnectionFactories.elements();
            while(e.hasMoreElements())
            {
                f.addElement(e.nextElement());
            }
	}
	java.util.Enumeration i = f.elements();
	while(i.hasMoreElements())
	{
	    ((IceInternal.IncomingConnectionFactory)i.nextElement()).flushBatchRequests();
	}
    }

    public synchronized void
    incDirectCount()
    {
	checkForDeactivation();

	if(IceUtil.Debug.ASSERT)
	{
	    IceUtil.Debug.Assert(_directCount >= 0);
	}
	++_directCount;
    }

    public synchronized void
    decDirectCount()
    {
	// No check for deactivation here!
	
	if(IceUtil.Debug.ASSERT)
	{
	    IceUtil.Debug.Assert(_instance != null); // Must not be called after waitForDeactivate().
	    IceUtil.Debug.Assert(_directCount > 0);
	}
	if(--_directCount == 0)
	{
	    notifyAll();
	}
    }

    public IceInternal.ServantManager
    getServantManager()
    {	
	// No mutex lock necessary, _instance is
	// immutable after creation until it is removed in
	// waitForDeactivate().
	
	// No check for deactivation here!
	
	if(IceUtil.Debug.ASSERT)
	{
	    IceUtil.Debug.Assert(_instance != null); // Must not be called after waitForDeactivate().
	}

	return _servantManager;
    }

    //
    // Only for use by IceInternal.ObjectAdapterFactory
    //
    public
    ObjectAdapter(IceInternal.Instance instance, Communicator communicator, String name, String endpointInfo)
    {
	_deactivated = false;
        _instance = instance;
	_communicator = communicator;
	_servantManager = new IceInternal.ServantManager(instance, name);
	_printAdapterReadyDone = false;
        _name = name;
	_id = instance.properties().getProperty(name + ".AdapterId");
	_replicaGroupId = instance.properties().getProperty(name + ".ReplicaGroupId");
	_directCount = 0;
	_waitForDeactivate = false;
	
        try
        {
	    //
	    // Parse the endpoints, but don't store them in the adapter.
	    // The connection factory might change it, for example, to
	    // fill in the real port number.
	    //
	    java.util.Vector endpoints = parseEndpoints(endpointInfo);
	    for(int i = 0; i < endpoints.size(); ++i)
	    {
		IceInternal.Endpoint endp = (IceInternal.Endpoint)endpoints.elementAt(i);
                _incomingConnectionFactories.addElement(
		    new IceInternal.IncomingConnectionFactory(instance, endp, this));
            }

	    //
	    // Parse published endpoints. These are used in proxies
	    // instead of the connection factory endpoints.
	    //
	    String endpts = _instance.properties().getProperty(name + ".PublishedEndpoints");
	    _publishedEndpoints = parseEndpoints(endpts);

	    String router = _instance.properties().getProperty(name + ".Router");
	    if(router.length() > 0)
	    {
		addRouter(RouterPrxHelper.uncheckedCast(_instance.proxyFactory().stringToProxy(router)));
	    }

	    String locator = _instance.properties().getProperty(name + ".Locator");
	    if(locator.length() > 0)
	    {
		setLocator(LocatorPrxHelper.uncheckedCast(_instance.proxyFactory().stringToProxy(locator)));
	    }
	    else
	    {
		setLocator(_instance.referenceFactory().getDefaultLocator());
	    }
        }
        catch(LocalException ex)
        {
	    deactivate();
	    waitForDeactivate();
            throw ex;
        }
    }

    protected synchronized void
    finalize()
        throws Throwable
    {
	if(!_deactivated)
	{
	    _instance.logger().warning("object adapter `" + _name + "' has not been deactivated");
	}
	else if(_instance != null)
	{
	    _instance.logger().warning("object adapter `" + _name + "' deactivation had not been waited for");
	}
	else
	{
	    IceUtil.Debug.FinalizerAssert(_servantManager == null);
	    IceUtil.Debug.FinalizerAssert(_communicator == null);
	    IceUtil.Debug.FinalizerAssert(_incomingConnectionFactories == null);
	    IceUtil.Debug.FinalizerAssert(_directCount == 0);
	    IceUtil.Debug.FinalizerAssert(!_waitForDeactivate);
	}
    }

    private ObjectPrx
    newProxy(Identity ident, String facet)
    {
	if(_id.length() == 0)
	{
	    return newDirectProxy(ident, facet);
	}
	else if(_replicaGroupId.length() == 0)
	{	    
	    return newIndirectProxy(ident, facet, _id);
	}
	else
	{
	    return newIndirectProxy(ident, facet, _replicaGroupId);
	}
    }

    private ObjectPrx
    newDirectProxy(Identity ident, String facet)
    {
        IceInternal.Endpoint[] endpoints;

	// 
	// Use the published endpoints, otherwise use the endpoints from all
	// incoming connection factories.
	//
	int sz = _publishedEndpoints.size();
	if(sz > 0)
	{
	    endpoints = new IceInternal.Endpoint[sz + _routerEndpoints.size()];
	    _publishedEndpoints.copyInto(endpoints);
	}
	else
	{
	    sz = _incomingConnectionFactories.size();
	    endpoints = new IceInternal.Endpoint[sz + _routerEndpoints.size()];
	    for(int i = 0; i < sz; ++i)
	    {
		IceInternal.IncomingConnectionFactory factory =
		    (IceInternal.IncomingConnectionFactory)_incomingConnectionFactories.elementAt(i);
		endpoints[i] = factory.endpoint();
	    }
	}

        //
        // Now we also add the endpoints of the router's server proxy, if
        // any. This way, object references created by this object adapter
        // will also point to the router's server proxy endpoints.
        //
        for(int i = 0; i < _routerEndpoints.size(); ++i)
        {
	    endpoints[sz + i] = (IceInternal.Endpoint)_routerEndpoints.elementAt(i);
        }

        //
        // Create a reference and return a proxy for this reference.
        //
        Connection[] connections = new Connection[0];
        IceInternal.Reference reference =
	    _instance.referenceFactory().create(ident, new java.util.Hashtable(), facet,
						IceInternal.Reference.ModeTwoway, false, endpoints, null);
        return _instance.proxyFactory().referenceToProxy(reference);
    }

    private ObjectPrx
    newIndirectProxy(Identity ident, String facet, String id)
    {
	//
	// Create a reference with the adapter id and return a
	// proxy for the reference.
	//
	IceInternal.Endpoint[] endpoints = new IceInternal.Endpoint[0];
	Connection[] connections = new Connection[0];
	IceInternal.Reference reference =
	    _instance.referenceFactory().create(ident, new java.util.Hashtable(), facet,
						IceInternal.Reference.ModeTwoway, false, id, null,
						_locatorInfo);
	return _instance.proxyFactory().referenceToProxy(reference);
    }

    private void
    checkForDeactivation()
    {
	if(_deactivated)
	{
            ObjectAdapterDeactivatedException ex = new ObjectAdapterDeactivatedException();
	    ex.name = _name;
	    throw ex;
	}
    }

    private static void
    checkIdentity(Identity ident)
    {
        if(ident.name == null || ident.name.length() == 0)
        {
            IllegalIdentityException e = new IllegalIdentityException();
            try
            {
                e.id = (Identity)ident.ice_clone();
            }
            catch(IceUtil.CloneException ex)
            {
		if(IceUtil.Debug.ASSERT)
		{
		    IceUtil.Debug.Assert(false);
		}
            }
            throw e;
        }

        if(ident.category == null)
        {
            ident.category = "";
        }
    }

    private java.util.Vector
    parseEndpoints(String endpts)
    {
        endpts = endpts.toLowerCase();

	int beg;
	int end = 0;

	final String delim = " \t\n\r";

	java.util.Vector endpoints = new java.util.Vector();
	while(end < endpts.length())
	{
	    beg = IceUtil.StringUtil.findFirstNotOf(endpts, delim, end);
	    if(beg == -1)
	    {
		break;
	    }

	    end = endpts.indexOf(':', beg);
	    if(end == -1)
	    {
		end = endpts.length();
	    }

	    if(end == beg)
	    {
		++end;
		continue;
	    }

	    String s = endpts.substring(beg, end);
	    IceInternal.Endpoint endp = _instance.endpointFactory().create(s);
	    if(endp == null)
	    {
	        Ice.EndpointParseException e = new Ice.EndpointParseException();
		e.str = s;
		throw e;
	    }
	    endpoints.addElement(endp);

	    ++end;
	}

	return endpoints;
    }

    private boolean _deactivated;
    private IceInternal.Instance _instance;
    private Communicator _communicator;
    private IceInternal.ServantManager _servantManager;
    private boolean _printAdapterReadyDone;
    private /*final*/ String _name;
    private /*final*/ String _id;
    private /*final*/ String _replicaGroupId;
    private java.util.Vector _incomingConnectionFactories = new java.util.Vector();
    private java.util.Vector _routerEndpoints = new java.util.Vector();
    private java.util.Vector _routerInfos = new java.util.Vector();
    private java.util.Vector _publishedEndpoints;
    private IceInternal.LocatorInfo _locatorInfo;
    private int _directCount;
    private boolean _waitForDeactivate;
}
