/*
 * @(#)ClientDelegate.java	1.83 01/12/03
 *
 * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

/*
 * Licensed Materials - Property of IBM
 * RMI-IIOP v1.0
 * Copyright IBM Corp. 1998 1999  All Rights Reserved
 *
 * US Government Users Restricted Rights - Use, duplication or
 * disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
 */
package com.sun.corba.se.internal.corba;

import java.util.Iterator;

import org.omg.CORBA.INTERNAL;
import org.omg.CORBA.ORB;
import org.omg.CORBA.portable.RemarshalException;
import org.omg.CORBA.SystemException;
import org.omg.CORBA.CompletionStatus;
import org.omg.CORBA.WrongTransaction;
import org.omg.CORBA.Request;
import org.omg.CORBA.NamedValue;
import org.omg.CORBA.NVList;
import org.omg.CORBA.Context;
import org.omg.CORBA.ContextList;
import org.omg.CORBA.ExceptionList;
import org.omg.CORBA.TypeCode;
import org.omg.CORBA.DATA_CONVERSION;
import org.omg.CORBA.UNKNOWN;

import org.omg.CORBA.portable.ObjectImpl;
import org.omg.CORBA.portable.InputStream;
import org.omg.CORBA.portable.OutputStream;
import org.omg.CORBA.portable.Delegate;
import org.omg.CORBA.portable.ServantObject;
import org.omg.CORBA.portable.ApplicationException;
import org.omg.CORBA.portable.UnknownException;
import org.omg.IOP.TAG_CODE_SETS;

import javax.rmi.CORBA.Util;
import javax.rmi.CORBA.Tie;

import java.rmi.RemoteException;

import com.sun.org.omg.SendingContext.CodeBase;

import com.sun.corba.se.internal.ior.IIOPProfile;
import com.sun.corba.se.internal.ior.IIOPProfileTemplate;
import com.sun.corba.se.internal.ior.ObjectKey;

import com.sun.corba.se.internal.core.IOR;
import com.sun.corba.se.internal.core.ClientGIOP;
import com.sun.corba.se.internal.core.ClientSubcontract;
import com.sun.corba.se.internal.core.ClientRequest;
import com.sun.corba.se.internal.core.ClientResponse;
import com.sun.corba.se.internal.core.ServiceContext;
import com.sun.corba.se.internal.core.ServiceContexts;
import com.sun.corba.se.internal.core.MarshalOutputStream;
import com.sun.corba.se.internal.core.MarshalInputStream;
import com.sun.corba.se.internal.core.CodeSetComponentInfo;
import com.sun.corba.se.internal.core.CodeSetConversion;
import com.sun.corba.se.internal.core.UEInfoServiceContext;
import com.sun.corba.se.internal.core.CodeSetServiceContext;
import com.sun.corba.se.internal.core.SendingContextServiceContext;
import com.sun.corba.se.internal.core.ORBVersionServiceContext;
import com.sun.corba.se.internal.core.NoSuchServiceContext;
import com.sun.corba.se.internal.core.DuplicateServiceContext;
import com.sun.corba.se.internal.core.GIOPVersion;
import com.sun.corba.se.internal.core.ORBVersion;

import com.sun.corba.se.internal.orbutil.MinorCodes;

import com.sun.corba.se.internal.util.JDKBridge;
import com.sun.corba.se.internal.orbutil.ORBUtility;

import com.sun.corba.se.internal.iiop.messages.ReplyMessage;
import com.sun.corba.se.internal.iiop.Connection;
import com.sun.corba.se.internal.iiop.ClientRequestImpl;
import com.sun.corba.se.internal.iiop.LocalClientRequestImpl;
import com.sun.corba.se.internal.iiop.messages.KeyAddr;
import com.sun.corba.se.internal.core.ORBVersionFactory;

import com.sun.corba.se.internal.ior.CodeSetsComponent;
import com.sun.corba.se.internal.ior.TaggedComponent;
import com.sun.corba.se.internal.orbutil.ORBConstants;

import com.sun.corba.se.internal.iiop.messages.KeyAddr;
import com.sun.corba.se.internal.iiop.messages.ProfileAddr;
import com.sun.corba.se.internal.iiop.messages.ReferenceAddr;

/**
 * ClientDelegate is the RMI client-side subcontract or representation
 * It implements RMI delegate as well as our internal ClientSubcontract
 * interface.
 */
public class ClientDelegate
    extends org.omg.CORBA_2_3.portable.Delegate
    implements ClientSubcontract
{
    protected void dprint( String msg )
    {
	ORBUtility.dprint( this, msg ) ;
    }

    protected com.sun.corba.se.internal.corba.ORB orb;
    protected IOR ior;
    protected IOR locatedIOR;
    protected boolean isCachedHashValue = false;
    protected int cachedHashValue;
    protected int scid; // my subcontract id
    protected ServantObject servant;
    protected ClassLoader servantClassLoader ;

    protected short addressingDisposition = KeyAddr.value;

    // the connection with which to store the codebase returned by the
    // server

    // orb version to be stored into thread local

    public ClientDelegate() {
	super();
	servant = null;      
    }

    public ClientDelegate(com.sun.corba.se.internal.corba.ORB orb,
			  IOR ior,
			  int id) 
    {
        this(orb,ior,id,null);
    }

    public ClientDelegate(com.sun.corba.se.internal.corba.ORB orb,
			  IOR ior,
			  int id,
			  java.lang.Object servant) 
    {
	this();
	setOrb(orb);
	this.ior = ior;
	this.locatedIOR = ior;
	this.scid = id;
	if (servant != null && servant instanceof Tie) {
	    servantClassLoader = servant.getClass().getClassLoader() ;
	    this.servant = new ServantObject();
	    this.servant.servant = ((Tie)servant).getTarget();
	} else {
	    this.servant = null;
	    servantClassLoader = null ;
	}                    
    }

    public void unexport() {
	// DO NOT set the IOR to null.  (Un)exporting is only concerns
	// the servant not the IOR.  If the ior is set to null then
	// null pointer exceptions happen during an colocated invocation.
	// It is better to let the invocation proceed and get OBJECT_NOT_EXIST
	// from the server side.
	//ior = null;
	servant = null;
    }

    public IOR marshal() {
	return ior;
    }

    public IOR getIOR() {
	return ior;
    }

    public IOR getLocatedIOR() {
        return locatedIOR;
    }

    public void unmarshal(IOR ior) {
	this.ior = ior;
	this.locatedIOR = ior;
    }

    public void setAddressingDisposition(short value){
        this.addressingDisposition = value;    
    }
    
    public short getAddressingDisposition() {
        return this.addressingDisposition;
    }
    
    public void setLocatedIOR( IOR locatedIOR ) {
        this.locatedIOR = locatedIOR;
    }

    public void setId(int id) {
	this.scid = id;
    }

    public int getId()
    {
	return scid ;
    }

    public void setOrb(com.sun.corba.se.internal.core.ORB orb) {
	this.orb = (com.sun.corba.se.internal.corba.ORB) orb;
        this.addressingDisposition = this.orb.getGIOPAddressDisposition();
    }

    /*
     * NOTE: This version of invoke should ONLY be used externally 
     * by a DII call (it is used internally by the streams invoke).
     * Otherwise the PortableInterceptor internal stack will become
     * unbalanced.
     *
     * It is invoked from the DII implementation: RequestImpl.doInvocation().
     *
     * invoke(org.omg.CORBA.Object, OutputStream) calls this one, so
     * do any service context processing here.
     *
     * This is also called from GenericPOAClientSC's
     * invoke(org.omg.CORBA.Object, OutputStream).
     */
    public ClientResponse invoke(ClientRequest request) {
	if (orb.subcontractDebugFlag)
	    dprint( "invoke(ClientRequest) called" ) ;

        ClientResponse resp = request.invoke();

        // Notify Portable Interceptors of client response.
	// This must happen here so that both stream-based and DII
	// calls have their hands on the response.
        orb.setClientPIInfo( resp );

        if (request.isOneWay()) {
            return null;
        }

        consumeServiceContexts(resp);

        // Now that we have the service contexts processed and the
        // correct ORBVersion set, we must finish initializing the
        // stream.
        resp.performORBVersionSpecificInit();


        // FIX(Ram J) (04/28/2000) The location forwarded behaviour and the
        // system exception(COMM_FAILURE) behaviour (i.e retry from root ior)
        // is handled in this.invoke(Object, OutputStream) which called this
        // method. The client stubs should not directly call this method.
        //  However, there are a few methods in the ClientDelegate : is_a(),
        // get_interface_def() and  non_existent() which directly call
        // this method. Such methods should handle the location forwarded
        // responses by building new requests and system exception(COMM_FAILURE)
        // responses by retrying from root ior.

        return resp;
    }

    /*
     * This version of invoke should be used by everthing but a DII call.
     */
    public InputStream invoke(org.omg.CORBA.Object self, OutputStream out)
	throws ApplicationException,
	       org.omg.CORBA.portable.RemarshalException
    {

        ClientRequest request = (ClientRequest) out;

        ClientResponse response = this.invoke(request);

        // For PI code:
        Exception exception = null;

        if (request.isOneWay()) { //oneway calls
            // Invoke Portable Interceptors with receive_other
            exception = orb.invokeClientPIEndingPoint(
                ReplyMessage.NO_EXCEPTION, exception );
            continueOrThrowSystemOrRemarshal( exception );
            return null;
        }

        // COMMENT(Ram J) 10/01/2000 There is no need to set the response
        // stream's connection to that of the request stream. The response
        // stream will have a connection associated if the response had been
        // built out of the server response; else if the response was built out
        // of SystemException arising out of response handling, then there
        // will be no connection or service context available with the response
        // stream. In such a case, the consumeServiceContexts has to return
        // gracefully.
        /*
	// consume service contexts that were returned to the client
	Connection c = request.getConnection();
	if (response != null)
            response.setConnection(c);
        */

        // Processing of service contexts is done in invoke(ClientRequest)

        if (response.isSystemException()) {

            SystemException se = response.getSystemException();

            boolean doRemarshal = false;

            // FIX(Ram J) (04/28/2000) added  locatedIOR = ior
            // and throw RemarshallException to force retrying
            // from root ior, if system exception is COMM_FAILURE.
            // WARNING: There is a risk of infinite loopback if the requests on
            // location forwarded ior result in system exception (COMM_FAILURE)
            if (se instanceof org.omg.CORBA.COMM_FAILURE
                && se.completed == CompletionStatus.COMPLETED_NO) {
                if (locatedIOR != ior) {
                    locatedIOR = ior; // retry from root ior

                    doRemarshal = true;
                }
            }

	    /* This used to live in a loop in IIOPOutputStream.invoke.
	       That loop had a retry count to bound the number of tries.
	       However that loop did not work once fragmentation was
	       put in.  So it was moved here.  However, there is no
	       place to hang the count since this throws out to the Stub.
	       We need invocation level (i.e., thread local) date like
	       in PI to put this count. That is scheduled for Taggart.
	       For now, just turn this off.
            if (se.minor == MinorCodes.CONN_CLOSE_REBIND && 
                (se instanceof org.omg.CORBA.COMM_FAILURE)) {

                doRemarshal = true;
            }
	    */

            if (doRemarshal) {
                    
		// Invoke Portable Interceptors with receive_exception:
		exception = orb.invokeClientPIEndingPoint(
                    ReplyMessage.SYSTEM_EXCEPTION, se );

		// If PI did not change the exception, throw a
		// Remarshal.  Otherwise, throw the exception PI
		// wants thrown.
		if( se == exception ) {
		    // exception = null is to maintain symmetry with
		    // GenericPOAClientSC.
		    //exception = null;
		    throw new RemarshalException();
                }
                else {
                    continueOrThrowSystemOrRemarshal( exception );
                    throw new INTERNAL("Assertion failed. " +
                                       "exception should not be null." );
                }
            }

            ServiceContexts contexts =  response.getServiceContexts();
            if (contexts != null) {
                try {
                    UEInfoServiceContext usc =
			(UEInfoServiceContext)
			contexts.get(UEInfoServiceContext.SERVICE_CONTEXT_ID);
                    Throwable unknown = usc.getUE() ;
                    UnknownException ue = new UnknownException(unknown);

                    // Invoke Portable Interceptors with receive_exception:
                    exception = orb.invokeClientPIEndingPoint(
                        ReplyMessage.SYSTEM_EXCEPTION, ue );

                    continueOrThrowSystemOrRemarshal( exception );
		    throw new INTERNAL(
			"Assertion failed.  exception should not be null." );
                } catch (NoSuchServiceContext exc) {
                    // NO-OP: handled by system exception below
                }
            }

            // Invoke Portable Interceptors with receive_exception:
            exception = orb.invokeClientPIEndingPoint(
                ReplyMessage.SYSTEM_EXCEPTION, se );

            continueOrThrowSystemOrRemarshal( exception );

            // Note: We should never need to execute this line, but
            // we should assert in case exception is null somehow.
            throw new INTERNAL(
		"Assertion failed: exception should not be null." );
        } else if (response.isUserException()) {
            ApplicationException appException =
                new ApplicationException(response.peekUserExceptionId(),
                           (org.omg.CORBA.portable.InputStream)response);

            // Invoke Portable Interceptors with receive_exception
            // (user exception):
            exception = orb.invokeClientPIEndingPoint(
                ReplyMessage.USER_EXCEPTION, appException );

            if( !(exception instanceof ApplicationException) ) {
                continueOrThrowSystemOrRemarshal( exception );
            }

            throw (ApplicationException)exception;
        } else if (response.isLocationForward()) {
            // FIX(Ram J) (04/28/2000) added setting locatedIOR
            locatedIOR = response.getForwardedIOR();

            // Invoke Portable Interceptors with receive_other:
	    Exception newException = orb.invokeClientPIEndingPoint(
		ReplyMessage.LOCATION_FORWARD, null );

            // For consistency with corresponding code in GenericPOAClientSC:
	    if( !(newException instanceof RemarshalException) ) {
		exception = newException;
	    }

            // If PI did not change exception, throw Remarshal, else
            // throw the exception PI wants thrown.
            if( exception != null ) {
                continueOrThrowSystemOrRemarshal( exception );
            }

            throw new org.omg.CORBA.portable.RemarshalException();
        } else if (response.isDifferentAddrDispositionRequested()) {
            // set the desired target addressing disposition.
            addressingDisposition = response.getAddrDisposition();

            // Invoke Portable Interceptors with receive_other:
	    Exception newException = orb.invokeClientPIEndingPoint(
		ReplyMessage.NEEDS_ADDRESSING_MODE, null);

            // For consistency with corresponding code in GenericPOAClientSC:
	    if( !(newException instanceof RemarshalException) ) {
		exception = newException;
	    }

            // If PI did not change exception, throw Remarshal, else
            // throw the exception PI wants thrown.
            if( exception != null ) {
                continueOrThrowSystemOrRemarshal( exception );
            }

            throw new org.omg.CORBA.portable.RemarshalException();            
        } else /* normal response */ {
            // Invoke Portable Interceptors with receive_reply:
            exception = orb.invokeClientPIEndingPoint(
                ReplyMessage.NO_EXCEPTION, null );

            // Remember: not thrown if exception is null.
            continueOrThrowSystemOrRemarshal( exception );

            return (InputStream) response;
        }
    }

    // Filters the given exception into a SystemException or a
    // RemarshalException and throws it.  Assumes the given exception is
    // of one of these two types.  This is a utility method for
    // the above invoke code which must do this numerous times.
    // If the exception is null, no exception is thrown.
    //
    // Note that this code is duplicated in GenericPOAClientSC.java
    private void continueOrThrowSystemOrRemarshal( Exception exception )
        throws SystemException, RemarshalException
    {
        if( exception == null ) {
            // do nothing.
        }
        else if( exception instanceof RemarshalException ) {
            throw (RemarshalException)exception;
        }
        else {
            throw (SystemException)exception;
        }
    }

    public ServiceContexts getServiceContexts( Connection c,
	int requestId, String opName, boolean isOneWay, GIOPVersion giopVersion )
    {
	ServiceContexts contexts = new ServiceContexts( orb ) ;

        addCodeSetServiceContext(c, contexts, giopVersion);

	// ORBVersion servicecontext needs to be sent
	ORBVersionServiceContext ovsc = new ORBVersionServiceContext(
	                ORBVersionFactory.getORBVersion() ) ;
	try {
	    contexts.put( ovsc ) ;
	} catch (DuplicateServiceContext dsc) {
	    throw new INTERNAL() ;
	}

	// NOTE : We only want to send the runtime context the first time
	if ((c != null) && !c.isPostInitialContexts()) {
	    c.setPostInitialContexts();
	    SendingContextServiceContext scsc =
		new SendingContextServiceContext( orb.getServantIOR() ) ; //d11638
	    try {
		contexts.put( scsc ) ;
	    } catch (DuplicateServiceContext dsc) {
		throw new INTERNAL() ;
	    }
	}

	return contexts ;
    }

    public void consumeServiceContexts(ClientResponse response)
    {
	ServiceContexts ctxts = response.getServiceContexts();
	ServiceContext sc ;

        if (ctxts == null) {
            return; // no service context available, return gracefully.
        }

	try {
	    sc = ctxts.get( SendingContextServiceContext.SERVICE_CONTEXT_ID ) ;

	    SendingContextServiceContext scsc =
		(SendingContextServiceContext)sc ;
	    IOR ior = scsc.getIOR() ;

	    try {
	        // set the codebase returned by the server
	        if (response.getConnection() != null) {
	            response.getConnection().setCodeBaseIOR(ior);
	        }
	    } catch (ThreadDeath td) {
		throw td ;
	    } catch (Throwable t) {
		throw new DATA_CONVERSION( MinorCodes.BAD_STRINGIFIED_IOR,
					   CompletionStatus.COMPLETED_NO);
	    }
	} catch (NoSuchServiceContext exc) {
	    // ignore: this type not present
	}

	// see if the version subcontract is present, if yes, then set
	// the ORBversion
	try {
	    sc = ctxts.get( ORBVersionServiceContext.SERVICE_CONTEXT_ID ) ;

	    ORBVersionServiceContext ovsc =
	       (ORBVersionServiceContext) sc;

	    ORBVersion version = ovsc.getVersion();
	    orb.setORBVersion( version ) ;
	} catch (NoSuchServiceContext exc) {
	    // ignore: this type not present

            // If we were talking to Kestrel, we would have set the
            // ORB version as such at invoke time after examining
            // the IOR.
	}
    }

    /*
     * This is the method used by the rest of the system to
     * create requests.  It is used both my streams-based stubs
     * and by DII-based calls (i.e., from RequestImpl.doInvocation()
     * before marshaling arguments.
     */
    public ClientRequest createRequest( String opName, boolean isOneWay )
    {
	// Create request object which is also the outputstream
	ClientRequest request = null;
	ClientGIOP giop = orb.getClientGIOP();
	int id = giop.allocateRequestId() ;

        // Initiate this request with Portable Interceptors.
	// This is done here so if the following COMM_FAILURE occurs we will
	// keep the PI ClientRequestInfo stack balanced.  The COMM_FAILURE
	// happens before invoking starting points in getConnection so we
	// don't need to invoke ending points.
        orb.initiateClientPIRequest( false );

	try {

	    request = createRequest( locatedIOR, opName, isOneWay, id );

	} catch (org.omg.CORBA.COMM_FAILURE ex) {
	    if (locatedIOR == ior) {
		throw ex;
	    }
	    // The COMM_FAILURE may have happened because the server died.
	    // Try creating the request with the original IOR.

	    request = createRequest(ior, opName, isOneWay, id);
	}

	return request;
    }

    /*
     * This helper method actually creates the approprieate
     * request object.
     */
    protected ClientRequest createRequest(IOR iorForThisRequest, String opName,
					  boolean isOneWay, int requestId)

    {
	IIOPProfile iop = iorForThisRequest.getProfile();
	ObjectKey  key = iop.getObjectKey();        
	Connection conn = null ;
	ServiceContexts svc = null ;

	GIOPVersion giopVersion = 
	    GIOPVersion.chooseRequestVersion(orb, iorForThisRequest);

	if (iorForThisRequest.isLocal()) {
	    svc = getServiceContexts( conn, requestId, opName, isOneWay, giopVersion ) ;

            // Set the sources of info for the Portable Interceptors
	    // ClientRequestInfo object, and invoke the client interceptor
	    // starting points.  Note these 2 lines are duplicated below.
	    // We explicitly pass in the effective target in
	    // case it is different than the locatedIOR field.  For example,
	    // in case of the COMM_FAILURE in the other createRequest().
            orb.setClientPIInfo( null, this, iorForThisRequest, iop, requestId,
				 opName, isOneWay, svc );
            try {
                orb.invokeClientPIStartingPoint();
            }
            catch( RemarshalException e ) {
                // If this is a forward request, recursively call createRequest
                // with the located IOR.

                return createRequest( locatedIOR, opName, isOneWay, requestId);
            }

	    return new LocalClientRequestImpl(
				GIOPVersion.V1_2,
				(com.sun.corba.se.internal.iiop.ORB)orb, 
				iorForThisRequest, this.addressingDisposition, 
                                opName, isOneWay, svc, requestId);
	} else {
            conn = orb.getClientGIOP().getConnection(iorForThisRequest);

            // Code set negotiation occurs on the first request
            if (conn != null && conn.getCodeSetContext() == null)
                performCodeSetNegotiation(conn, giopVersion);
            
	    svc = getServiceContexts( conn, requestId, opName, isOneWay, giopVersion ) ;

            // Set Portable Interceptors info.  See above comment.
            orb.setClientPIInfo( conn, this, iorForThisRequest, iop, requestId,
				 opName, isOneWay, svc );
            try {
                orb.invokeClientPIStartingPoint();
            }
            catch( RemarshalException e ) {
                // If this is a forward request, recursively call createRequest
                // with the located IOR.

                return createRequest( locatedIOR, opName, isOneWay, requestId);
            }

	    return new ClientRequestImpl(giopVersion, iorForThisRequest, 
                                         this.addressingDisposition, 
                                         opName, isOneWay, svc,
					 requestId, conn);
	}
    }

    // Invoked by DII layer (from RequestImpl.doInvocation())
    // after unmarshaling reply and exceptions.
    public void releaseReply(ClientResponse resp, String method,
			     Exception exception)
	throws WrongTransaction, SystemException
    {
    }

    public void releaseReply(org.omg.CORBA.Object self, 
			     InputStream input)
    {
	orb.sendCancelRequestIfFinalFragmentNotSent();

        // Invoke Portable Interceptors cleanup.  This is done to handle
        // exceptions during stream marshaling.  More generally, exceptions
        // that occur in the ORB after send_request (which includes
        // after returning from _request) before _invoke:
        orb.cleanupClientPIRequest();
    }

    public boolean is_equivalent(org.omg.CORBA.Object obj,
				 org.omg.CORBA.Object ref)
    {
	if ( ref == null )
	    return false;
	ObjectImpl oi = (ObjectImpl)ref;

	ClientSubcontract del = (ClientSubcontract)oi._get_delegate();
	if (del == this)
	    return true;
	if (!(del instanceof ClientDelegate))
	    return false;
	return ior.isEquivalent(del.marshal());
    }

    public int hash(org.omg.CORBA.Object obj, int maximum)
    {
	int h = this.hashCode();
	if ( h > maximum )
	    return 0;
	return h;
    }

    public int hashCode()
    {
	// This is not synchronized since the returned value is constant
	// so the overhead of synchronization is unnecessary.
	if (! isCachedHashValue) {
	    cachedHashValue = ior.stringify().hashCode();
	    isCachedHashValue = true;
	}
	return cachedHashValue;
    }

    public org.omg.CORBA.Object duplicate(org.omg.CORBA.Object orig) {
	return orig;
    }

    public void release(org.omg.CORBA.Object obj) {
	// DONT clear out internal variables to release memory !!
	// This delegate may be pointed-to by other objrefs !
    }

    public org.omg.CORBA.Request create_request(org.omg.CORBA.Object obj,
						org.omg.CORBA.Context ctx,
						String operation,
						org.omg.CORBA.NVList arg_list,
						org.omg.CORBA.NamedValue result)
    {
	return new RequestImpl(orb, obj, ctx, operation, arg_list,
			       result, null, null);
    }

    public org.omg.CORBA.Request request(org.omg.CORBA.Object obj,
					 String operation)
    {
	return new RequestImpl(orb, obj, null, operation, null, null, null,
			       null);
    }

    public org.omg.CORBA.ORB orb(org.omg.CORBA.Object self)
    {
	return this.orb;
    }

    public org.omg.CORBA.Request create_request(org.omg.CORBA.Object obj,
						org.omg.CORBA.Context ctx,
						String operation,
						org.omg.CORBA.NVList arg_list,
						org.omg.CORBA.NamedValue result,
						org.omg.CORBA.ExceptionList exclist,
						org.omg.CORBA.ContextList ctxlist)
    {
	return new RequestImpl(orb, obj, ctx, operation, arg_list, result,
			       exclist, ctxlist);
    }

    private static String AppExcInSpecialMethod =
	"ApplicationException in SpecialMethod - should not happen";

    public org.omg.CORBA.Object get_interface_def(org.omg.CORBA.Object obj) 
    {
	InputStream is = null;
        try {
	    ClientRequest os = createRequest("_interface", false);
	    is = (InputStream) invoke((org.omg.CORBA.Object)null,
				      (OutputStream)os);

	    ObjectImpl objimpl = (ObjectImpl) is.read_Object();

	    // check if returned object is of correct type
	    if ( !objimpl._is_a("IDL:omg.org/CORBA/InterfaceDef:1.0") )
                throw new org.omg.CORBA.UNKNOWN(
                    "InterfaceDef object of wrong type returned by server", 0,
                    CompletionStatus.COMPLETED_MAYBE);

	    // instantiate the stub
	    ObjectImpl stub;
	    try {
                stub = (ObjectImpl)
                    JDKBridge.loadClass("org.omg.CORBA._InterfaceDefStub").
		        newInstance();
	    } catch (Exception ex) {
                throw new org.omg.CORBA.UNKNOWN(
		    "org.omg.CORBA._InterfaceDefStub class not available", 0,
                    CompletionStatus.COMPLETED_NO);
	    }

	    org.omg.CORBA.portable.Delegate del = objimpl._get_delegate();
	    stub._set_delegate(del);

	    return stub;

	} catch (ApplicationException e) {
	    // This cannot happen.
	    throw new INTERNAL(AppExcInSpecialMethod);
	} catch (RemarshalException e) {
	    return get_interface_def(obj);
	} finally {
	    releaseReply((org.omg.CORBA.Object)null, (InputStream)is);
        }
    }

    public boolean is_a(org.omg.CORBA.Object obj, String dest) 
    {
        // dest is the typeId of the interface to compare against.
        // repositoryIds is the list of typeIds that the stub knows about.

        // First we look for an answer using local information.

        String [] repositoryIds = ((ObjectImpl)obj)._ids();
        String myid = ior.getTypeId();
        if ( dest.equals(myid) ) {
            return true;
	}
        for ( int i=0; i<repositoryIds.length; i++ ) {
            if ( dest.equals(repositoryIds[i]) ) {
		return true;
	    }
	}

        // But repositoryIds may not be complete, so it may be necessary to
        // go to server.

	InputStream is = null;
        try {
            ClientRequest os = createRequest("_is_a", false);
            os.write_string(dest);
            is = (InputStream) invoke((org.omg.CORBA.Object) null,
				      (OutputStream)os);

	    return is.read_boolean();

        } catch (ApplicationException e) {
	    // This cannot happen.
	    throw new INTERNAL(AppExcInSpecialMethod);
	} catch (RemarshalException e) {
	    return is_a(obj, dest);
	} finally {
	    releaseReply((org.omg.CORBA.Object)null, (InputStream)is);
        }
    }

    public boolean non_existent(org.omg.CORBA.Object obj) 
    {
	InputStream is = null;
        try {
            ClientRequest os = createRequest("_non_existent", false);
            is = (InputStream) invoke((org.omg.CORBA.Object)null,
				      (OutputStream)os);

	    return is.read_boolean();

	} catch (ApplicationException e) {
	    // This cannot happen.
	    throw new INTERNAL(AppExcInSpecialMethod);
	} catch (RemarshalException e) {
	    return non_existent(obj);
	} finally {
	    releaseReply((org.omg.CORBA.Object)null, (InputStream)is);
        }
    }

    public OutputStream request(org.omg.CORBA.Object self,
				String operation,
				boolean responseExpected) {
	return (OutputStream) createRequest(operation, !responseExpected);
    }

    /**
     * Returns true if this object is implemented by a local servant.
     *
     * @param self The object reference which delegated to this delegate.
     * @return true only if the servant incarnating this object is located in
     * this Java VM. Return false if the servant is not local or the ORB
     * does not support local stubs for this particular servant. The default
     * behavior of is_local() is to return false.
     */
    public boolean is_local(org.omg.CORBA.Object self) {
        // FIX (Bug # 4427356)
	//return servant != null;
	// Note that we MUST check whether servant is null, since returning
	// is_local TRUE with a null servant will cause a stack overflow in
	// the rmi-iiop stub.

	/*
	ClassLoader myClassLoader = self.getClass().getClassLoader() ;
        return 
	    (myClassLoader == servantClassLoader) &&
	    orb.allowLocalOptimization && 
	    ior.isLocal() && 
	    (servant != null) ;
	*/

	return ior.isLocal() ;
    }

    public boolean useLocalInvocation( org.omg.CORBA.Object self )
    {
	return false ;
    }

    /**
     * Returns a Java reference to the servant which should be used for this
     * request. servant_preinvoke() is invoked by a local stub.
     * If a ServantObject object is returned, then its servant field
     * has been set to an object of the expected type (Note: the object may
     * or may not be the actual servant instance). The local stub may cast
     * the servant field to the expected type, and then invoke the operation
     * directly. The ServantRequest object is valid for only one invocation,
     * and cannot be used for more than one invocation.
     *
     * @param self The object reference which delegated to this delegate.
     *
     * @param operation a string containing the operation name.
     * The operation name corresponds to the operation name as it would be
     * encoded in a GIOP request.
     *
     * @param a Class object representing the expected type of the servant.
     * The expected type is the Class object associated with the operations
     * class of the stub's interface (e.g. A stub for an interface Foo,
     * would pass the Class object for the FooOperations interface).
     *
     * @return a ServantObject object.
     * The method may return a null value if it does not wish to support
     * this optimization (e.g. due to security, transactions, etc).
     * The method must return null if the servant is not of the expected type.
     */
    public ServantObject servant_preinvoke(org.omg.CORBA.Object self,
					   String operation,
					   Class expectedType) {
	if (servant != null &&
	    expectedType.isAssignableFrom(servant.servant.getClass())) {

	    // _REVISIT_ Security check? If we put a security check
	    // on the client side of a connection (IIOPConnection.send()?)
	    // to ensure that the client has permission, do so here as
	    // well. Since a permission check is done when the socket
	    // is initially opened, but not on each use, we probably
	    // need to do the check ourselves since the call stack can
	    // be completely different each time, and a new caller may
	    // not have permission.

	    return servant;
	}
	return null;
    }

    /* Returns the codebase for object reference provided.
     * @param self the object reference whose codebase needs to be returned.
     * @return the codebase as a space delimited list of url strings or
     * null if none.
     */
    public String get_codebase(org.omg.CORBA.Object self) {
	if (ior != null) {
	    return ior.getCodebase();
	}
	return null;
    }

    public String toString() {
	return ior.stringify();
    }

    /**
     * This method overrides the org.omg.CORBA.portable.Delegate.equals method,
     * and does the equality check based on IOR equality.
     */
    public boolean equals(org.omg.CORBA.Object self, java.lang.Object other) {

        if (!(other instanceof ObjectImpl)) {
            return false;   
        }
        
        ObjectImpl otherObj = (ObjectImpl) other;
        if (otherObj._get_delegate() instanceof ClientDelegate) {
            ClientDelegate otherDel = (ClientDelegate) otherObj._get_delegate();
            IOR otherIor = otherDel.getIOR();
            return this.ior.equals(otherIor);
        } else if (otherObj._get_delegate() instanceof Delegate) {
            return super.equals(self, other);   
        }

        return false;
    }
    
    private void performCodeSetNegotiation(Connection conn,
                                           GIOPVersion giopVersion) {

        // conn.getCodeSetContext() is null when no other requests have
        // been made on this connection to trigger code set negotation.
        if (conn != null &&
            conn.getCodeSetContext() == null &&
            !giopVersion.equals(GIOPVersion.V1_0)) {
                        
            synchronized(conn) {
                // Double checking.  Don't let any other
                // threads use this connection until the
                // code sets are straight.
                if (conn.getCodeSetContext() != null)
                    return;
                
                // This only looks at the first code set component.  If
                // there can be multiple locations with multiple code sets,
                // this requires more work.
                IIOPProfileTemplate temp = getLocatedIOR().getProfile().getTemplate();
                Iterator iter = temp.iteratorById(TAG_CODE_SETS.value);
                if (!iter.hasNext()) {
                    // Didn't have a code set component.  The default will
                    // be to use ISO8859-1 for char data and throw an
                    // exception if wchar data is used.
                    return;
                }

                // Get the native and conversion code sets the
                // server specified in its IOR
                CodeSetComponentInfo serverCodeSets
                    = ((CodeSetsComponent)iter.next()).getCodeSetComponentInfo();

                // Perform the negotiation between this ORB's code sets and
                // the ones from the IOR
                CodeSetComponentInfo.CodeSetContext result
                    = CodeSetConversion.impl().negotiate(conn.getORB().getCodeSetComponentInfo(),
                                                         serverCodeSets);
                
                conn.setCodeSetContext(result);
            }
        }
    }

    private void addCodeSetServiceContext(Connection conn,
                                          ServiceContexts ctxs,
                                          GIOPVersion giopVersion) {

        // REVISIT.  OMG issue 3318 concerning sending the code set
        // service context more than once was deemed too much for the
        // RTF.  Here's our strategy for the moment:
        //
        // Send it on every request (necessary in cases of fragmentation
        // with multithreaded clients or when the first thing on a
        // connection is a LocateRequest).  Provide an ORB property
        // to disable multiple sends.
        //
        // Note that the connection is null in the local case and no
        // service context is included.  We use the ORB provided
        // encapsulation streams.
        //
        // Also, there will be no negotiation or service context
        // in GIOP 1.0.  ISO8859-1 is used for char/string, and
        // wchar/wstring are illegal.
        //
        if (giopVersion.equals(GIOPVersion.V1_0) || conn == null)
            return;
        
        CodeSetComponentInfo.CodeSetContext codeSetCtx = null;

        if (conn.getORB().alwaysSendCodeSetServiceContext() ||
            !conn.isPostInitialContexts()) {

            // Get the negotiated code sets (if any) out of the connection
            codeSetCtx = conn.getCodeSetContext();
        }

        // Either we shouldn't send the code set service context, or
        // for some reason, the connection doesn't have its code sets.
        // Perhaps the server didn't include them in the IOR.  Uses
        // ISO8859-1 for char and makes wchar/wstring illegal.
        if (codeSetCtx == null)
            return;

        CodeSetServiceContext cssc = new CodeSetServiceContext(codeSetCtx);
        try {
            ctxs.put(cssc);
        } catch (DuplicateServiceContext dsc) {
            // Ignore
        }
    }    
}
