/*
 * @(#)CDREncapsCodec.java	1.16 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.Interceptors;

import org.omg.CORBA.Any;
import org.omg.CORBA.BAD_PARAM;
import org.omg.CORBA.CompletionStatus;
import org.omg.CORBA.ORB;
import org.omg.CORBA.TypeCode;
import org.omg.CORBA.LocalObject;

import com.sun.corba.se.internal.corba.AnyImpl;
import com.sun.corba.se.internal.core.GIOPVersion;
import com.sun.corba.se.internal.corba.EncapsInputStream;
import com.sun.corba.se.internal.corba.EncapsOutputStream;
import com.sun.corba.se.internal.orbutil.MinorCodes;

import org.omg.IOP.Codec;
import org.omg.IOP.CodecPackage.FormatMismatch;
import org.omg.IOP.CodecPackage.InvalidTypeForEncoding;
import org.omg.IOP.CodecPackage.TypeMismatch;

/**
 * CDREncapsCodec is an implementation of Codec, as described
 * in orbos/99-12-02, that supports CDR encapsulation version 1.0, 1.1, and
 * 1.2.  
 */
public final class CDREncapsCodec 
    extends org.omg.CORBA.LocalObject 
    implements Codec 
{
    // The ORB that created the factory this codec was created from
    private ORB orb;

    // The GIOP version we are encoding for
    private GIOPVersion giopVersion;

    /*
     *******************************************************************
     * NOTE: CDREncapsCodec must remain immutable!  This is so that we
     * can pre-create CDREncapsCodecs for each version of GIOP in
     * CodecFactoryImpl.
     *******************************************************************/

    /**
     * Creates a new codec implementation.  Uses the given ORB to create
     * CDRInputStreams when necessary.
     *
     * @param orb The ORB to use to create a CDRInputStream or CDROutputStream
     * @param major The major version of GIOP we are encoding for
     * @param minor The minor version of GIOP we are encoding for
     */
    public CDREncapsCodec( ORB orb, int major, int minor ) {
        this.orb = orb;

        giopVersion = GIOPVersion.getInstance( (byte)major, (byte)minor );
    }

    /**
     * Convert the given any into a CDR encapsulated octet sequence 
     */
    public byte[] encode( Any data ) 
        throws InvalidTypeForEncoding 
    {
	if( data == null ) nullParam();
        return encodeImpl( data, true );
    }

    /**
     * Decode the given octet sequence into an any based on a CDR 
     * encapsulated octet sequence.
     */
    public Any decode ( byte[] data ) 
        throws FormatMismatch 
    {
	if( data == null ) nullParam();
	return decodeImpl( data, null );
    }

    /**
     * Convert the given any into a CDR encapsulated octet sequence.  Only
     * the data is stored.  The type code is not.
     */
    public byte[] encode_value( Any data ) 
        throws InvalidTypeForEncoding 
    {
	if( data == null ) nullParam();
        return encodeImpl( data, false );
    }

    /**
     * Decode the given octet sequence into an any based on a CDR 
     * encapsulated octet sequence.  The type code is expected not to appear
     * in the octet sequence, and the given type code is used instead.
     */
    public Any decode_value( byte[] data, TypeCode tc ) 
        throws FormatMismatch, TypeMismatch
    {
	if( data == null ) nullParam();
	if( tc == null ) nullParam();
	return decodeImpl( data, tc );
    }

    /**
     * Convert the given any into a CDR encapsulated octet sequence.  
     * If sendTypeCode is true, the type code is sent with the message, as in
     * a standard encapsulation.  If it is false, only the data is sent.
     * Either way, the endian type is sent as the first part of the message.
     */
    private byte[] encodeImpl( Any data, boolean sendTypeCode ) 
        throws InvalidTypeForEncoding 
    {
	if( data == null ) nullParam();

	// _REVISIT_ Note that InvalidTypeForEncoding is never thrown in
	// the body of this method.  This is due to the fact that CDR*Stream
	// will never throw an exception if the encoding is invalid.  To
	// fix this, the CDROutputStream must know the version of GIOP it
	// is encoding for and it must check to ensure that, for example,
	// wstring cannot be encoded in GIOP 1.0.
	//
	// As part of the GIOP 1.2 work, the CDRInput and OutputStream will
	// be versioned.  This can be handled once this work is complete.

	// Create output stream with default endianness.
	EncapsOutputStream cdrOut = new EncapsOutputStream( orb,
                                                            giopVersion );

	// This is an encapsulation, so put out the endian:
	cdrOut.putEndian();

	// Sometimes encode type code:
	if( sendTypeCode ) {
	    cdrOut.write_TypeCode( data.type() );
        }

	// Encode value and return.
	data.write_value( cdrOut );

	return cdrOut.toByteArray();
    }

    /**
     * Decode the given octet sequence into an any based on a CDR 
     * encapsulated octet sequence.  If the type code is null, it is
     * expected to appear in the octet sequence.  Otherwise, the given
     * type code is used.
     */
    private Any decodeImpl( byte[] data, TypeCode tc ) 
        throws FormatMismatch 
    {
	if( data == null ) nullParam();

	AnyImpl any = null;  // return value

	// _REVISIT_ Currently there is no way for us to distinguish between
	// a FormatMismatch and a TypeMismatch because we cannot get this
	// information from the CDRInputStream.  If a RuntimeException occurs,
	// it is turned into a FormatMismatch exception.

	try {
	    EncapsInputStream cdrIn = new EncapsInputStream( orb, data, 
                data.length, giopVersion );

	    cdrIn.consumeEndian();

	    // If type code not specified, read it from octet stream:
	    if( tc == null ) {
		tc = cdrIn.read_TypeCode();
	    }

	    // Create a new Any object:
	    any = new AnyImpl( orb );
	    any.read_value( cdrIn, tc );
	}
	catch( RuntimeException e ) {
	    // See above note.  
	    throw new FormatMismatch();
	}

	return any;
    }

    /**
     * Called when an invalid null parameter was passed.  Throws a
     * BAD_PARAM with a minor code of 1
     */
    private void nullParam() 
        throws BAD_PARAM 
    {
	throw new BAD_PARAM( MinorCodes.NULL_PARAM,
                             CompletionStatus.COMPLETED_NO );
    }

}
