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

package com.sun.corba.se.internal.iiop;

import java.io.IOException;
import java.util.Iterator;

import org.omg.CORBA.SystemException;
import org.omg.CORBA.CompletionStatus;
import org.omg.CORBA.INTERNAL;
import org.omg.CORBA.MARSHAL;
import org.omg.CORBA.DATA_CONVERSION;
import org.omg.CORBA.BAD_PARAM;
import org.omg.CORBA.INV_OBJREF;
import org.omg.CORBA.COMM_FAILURE;
import org.omg.CORBA.TRANSIENT;
import org.omg.CORBA.NO_RESOURCES;
import org.omg.CORBA.Principal;
import org.omg.CORBA.TypeCode;
import org.omg.CORBA.Any;
import org.omg.CORBA.portable.*;
import com.sun.corba.se.internal.util.Utility; //d11638
import com.sun.corba.se.internal.orbutil.MinorCodes; //d11638
import com.sun.corba.se.internal.core.ClientResponse;
import com.sun.corba.se.internal.core.IOR;
import com.sun.corba.se.internal.core.GIOPVersion;
import com.sun.corba.se.internal.iiop.messages.Message;
import com.sun.corba.se.internal.iiop.messages.MessageBase;

import com.sun.corba.se.internal.core.CodeSetConversion;
import com.sun.corba.se.internal.core.OSFCodeSetRegistry;
import com.sun.corba.se.internal.core.CodeSetComponentInfo;

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

public class IIOPOutputStream extends CDROutputStream
{
    protected Connection conn;
    protected Message msg;

    /* 
    Extraneous constructor
    Marked for removing
    public IIOPOutputStream(ORB orb, Connection c) {
        this(orb,
             orb.getGIOPVersion(),
             c);
    }
    */

    public IIOPOutputStream(GIOPVersion version, ORB orb, Connection c)
    {
        super(orb, version);

        getBufferManager().setIIOPOutputStream(this);

        this.conn = c;
    }

    // this is called during locate msg creation to force no fragmentation
    // for LocateRequest & LocateReply msgs for GIOP version 1.0 & 1.1.
    public IIOPOutputStream(GIOPVersion version, ORB orb, Connection c,
            boolean allowFragmentation) {
        super(orb, version, allowFragmentation);

        getBufferManager().setIIOPOutputStream(this);

        this.conn = c;
    }

    public IIOPOutputStream(IIOPInputStream s) {
        this(s.getGIOPVersion(),
             (com.sun.corba.se.internal.iiop.ORB)s.orb(), 
             s.getConnection());
    }

    public final Connection getConnection() {
	return conn;
    }

    // once request stream is created, its connection should not be altered.
    /*
    public final void setConnection(Connection c) {
	this.conn = c;
    }
    */

    public final Message getMessage() {
	return msg;
    }

    public final void setMessage(Message msg) {
	this.msg = msg;
    }

    // Removes the connection from the cache
    private void deleteCurrentConnection()
    {
        GIOPImpl clientGIOP = (GIOPImpl)(((com.sun.corba.se.internal.core.ORB)orb()).getClientGIOP());
        if (clientGIOP != null)
            clientGIOP.deleteConnection(((IIOPConnection)conn).getEndpoint());
    }

    public IIOPInputStream invoke(boolean isOneway)
    {
        ClientResponse response = null;
        SystemException systemEx = null;

        try {

            // Sends either the entire message or the last fragment
            finishSendingMessage();

            // Call getResponse here even if it's a oneway call in order
            // to remove the out call descriptor
            response = (ClientResponse)((IIOPConnection)conn).getResponse(isOneway,
                                                                          MessageBase.getRequestId(getMessage()));

            if (isOneway)
                return null;

            if (response.isSystemException()) {
                systemEx = response.getSystemException();

                if (systemEx == null)
                    throw new INTERNAL("getSystemException returned null in IIOPOutputStream",
                                       MinorCodes.GET_SYSTEM_EX_RETURNED_NULL,
                                       CompletionStatus.COMPLETED_MAYBE);
            } else
                return (IIOPInputStream)response;

        } catch (SystemException ex) {
            systemEx = ex;
        }

        if (systemEx.completed == CompletionStatus.COMPLETED_NO &&
	    systemEx.getClass() == COMM_FAILURE.class ) {
            deleteCurrentConnection();
        }

        // Always return null even if we got a SystemException
        // for oneways. 
        if (isOneway)
            return null;

        // A response from the server may have service contexts
        if (response != null)
            return (IIOPInputStream)response;
        else
            return new ClientResponseImpl(systemEx);
    }

    public final void finishSendingMessage() {
        getBufferManager().sendMessage();
    }

    /**
     * Write the contents of the CDROutputStream to the specified
     * output stream.  Has the side-effect of pushing any current
     * Message onto the Message list.
     * @param s The output stream to write to.
     */
    public void writeTo(java.io.OutputStream s) throws java.io.IOException {

        //
        // Update the GIOP MessageHeader size field.
        //

        ByteBufferWithInfo bbwi = getByteBufferWithInfo();

        msg.setSize(bbwi.buf, bbwi.getSize());

        com.sun.corba.se.internal.corba.ORB ourORB = 
            (com.sun.corba.se.internal.corba.ORB)orb();

        if (ourORB != null && ourORB.giopDebugFlag) {
            ORBUtility.dprint(this, "Sending message:");
            CDROutputStream_1_0.printBuffer(bbwi);
        }

        s.write(bbwi.buf, 0, bbwi.getSize());

        // TimeStamp connection to indicate it has been used
        // Note granularity of connection usage is assumed for
        // now to be that of a IIOP packet.
        conn.stampTime();
    }

    /** overrides create_input_stream from CDROutputStream */
    public org.omg.CORBA.portable.InputStream create_input_stream()
    {
        return new IIOPInputStream((com.sun.corba.se.internal.iiop.ORB)orb(), getByteBuffer(), getIndex(), isLittleEndian(), msg, conn);
    }

    // static methods

   /*
    * This chooses the right buffering strategy for the locate msg.
    * locate msgs 1.0 & 1.1 :=> grow, 1.2 :=> stream
    */
    public static IIOPOutputStream createIIOPOutputStreamForLocateMsg(
            GIOPVersion giopVersion, ORB orb, Connection c) {
        if (giopVersion.lessThan(GIOPVersion.V1_2)) { // no fragmentation
            return new IIOPOutputStream(giopVersion, orb, c, false);
        } else {
            return new IIOPOutputStream(giopVersion, orb, c);
        }
    }
    
    /**
     * Override the default CDR factory behavior to get the
     * negotiated code sets from the connection.
     *
     * These are only called once per message, the first time needed.
     *
     * In the local case, there is no Connection, so use the
     * local code sets.
     */
    protected CodeSetConversion.CTBConverter createCharCTBConverter() {
        CodeSetComponentInfo.CodeSetContext codesets = getCodeSets();

        // If the connection doesn't have its negotiated
        // code sets by now, fall back on the defaults defined
        // in CDRInputStream.
        if (codesets == null)
            return super.createCharCTBConverter();
        
        OSFCodeSetRegistry.Entry charSet
            = OSFCodeSetRegistry.lookupEntry(codesets.getCharCodeSet());

        if (charSet == null)
            throw new MARSHAL("Unknown char set: " + charSet,
                              MinorCodes.UNKNOWN_CODESET,
                              CompletionStatus.COMPLETED_NO);

        return CodeSetConversion.impl().getCTBConverter(charSet, 
                                                        isLittleEndian(), 
                                                        false);
    }

    protected CodeSetConversion.CTBConverter createWCharCTBConverter() {

        CodeSetComponentInfo.CodeSetContext codesets = getCodeSets();

        // If the connection doesn't have its negotiated
        // code sets by now, we have to throw an exception.
        // See CORBA formal 00-11-03 13.9.2.6.
        if (codesets == null) {
            if (conn.isServer())
                throw new BAD_PARAM(MinorCodes.NO_CLIENT_WCHAR_CODESET_CTX,
                                    CompletionStatus.COMPLETED_MAYBE);
            else
                throw new INV_OBJREF(MinorCodes.NO_SERVER_WCHAR_CODESET_CMP,
                                     CompletionStatus.COMPLETED_NO);
        }

        OSFCodeSetRegistry.Entry wcharSet
            = OSFCodeSetRegistry.lookupEntry(codesets.getWCharCodeSet());

        if (wcharSet == null)
            throw new MARSHAL("Unknown wchar set: " + wcharSet,
                              MinorCodes.UNKNOWN_CODESET,
                              CompletionStatus.COMPLETED_NO);

        boolean useByteOrderMarkers
            = ((com.sun.corba.se.internal.corba.ORB)orb()).useByteOrderMarkers();

        // With UTF-16:
        //
        // For GIOP 1.2, we can put byte order markers if we want to, and
        // use the default of big endian otherwise.  (See issue 3405b)
        //
        // For GIOP 1.1, we don't use BOMs and use the endianness of
        // the stream.
        if (wcharSet == OSFCodeSetRegistry.UTF_16) {
            if (getGIOPVersion().equals(GIOPVersion.V1_2)) {
                return CodeSetConversion.impl().getCTBConverter(wcharSet, 
                                                                false, 
                                                                useByteOrderMarkers);
            }

            if (getGIOPVersion().equals(GIOPVersion.V1_1)) {
                return CodeSetConversion.impl().getCTBConverter(wcharSet,
                                                                isLittleEndian(),
                                                                false);
            }
        }

        // In the normal case, let the converter system handle it
        return CodeSetConversion.impl().getCTBConverter(wcharSet, 
                                                        isLittleEndian(),
                                                        useByteOrderMarkers);
    }

    // If we're local and don't have a Connection, use the
    // local code sets, otherwise get them from the connection.
    // If the connection doesn't have negotiated code sets
    // yet, then we use ISO8859-1 for char/string and wchar/wstring
    // are illegal.
    private CodeSetComponentInfo.CodeSetContext getCodeSets() {
        if (conn == null)
            return CodeSetComponentInfo.LOCAL_CODE_SETS;
        else
            return conn.getCodeSetContext();
    }
}

