/*
 * @(#)DynamicMethodMarshallerImpl.java	1.14 10/03/23
 *
 * Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

package com.sun.corba.se.impl.presentation.rmi ;

import java.io.Serializable ;
import java.io.Externalizable ;

import javax.rmi.PortableRemoteObject ;
import javax.rmi.CORBA.Util ;

import org.omg.CORBA.portable.IDLEntity ;

import org.omg.CORBA_2_3.portable.InputStream ;
import org.omg.CORBA_2_3.portable.OutputStream ;
import org.omg.CORBA.portable.ApplicationException ;

import java.lang.reflect.Method ;

import java.rmi.RemoteException ;

import com.sun.corba.se.spi.orb.ORB ;

import com.sun.corba.se.spi.presentation.rmi.DynamicMethodMarshaller ;

public class DynamicMethodMarshallerImpl implements DynamicMethodMarshaller 
{
    Method method ;
    ExceptionHandler ehandler ;
    boolean hasArguments = true ;
    boolean hasVoidResult = true ;
    boolean needsArgumentCopy ;		// true if copyObjects call needs for args
    boolean needsResultCopy ;		// true if copyObject call needs for result
    ReaderWriter[] argRWs = null ;
    ReaderWriter resultRW = null ;

    private static boolean isAnyClass( Class cls )
    {
	return cls.equals( Object.class ) || cls.equals( Serializable.class ) ||
	    cls.equals( Externalizable.class ) ;
    }

    // Assume that cls is not Remote, !isAnyClass(cls), and 
    // !org.omg.CORBA.Object.class.isAssignableFrom( cls ).
    // Then return whether cls is an RMI-IIOP abstract interface.
    private static boolean isAbstractInterface( Class cls ) 
    {
	// Either cls is an interface that extends IDLEntity, or else
	// cls does not extend java.rmi.Remote and all of its methods
	// throw RemoteException.
	if (IDLEntity.class.isAssignableFrom( cls ))
	    return cls.isInterface() ;
	else 
	    return cls.isInterface() && allMethodsThrowRemoteException( cls ) ;
    }

    private static boolean allMethodsThrowRemoteException( Class cls ) 
    {
	Method[] methods = cls.getMethods() ;

	// Check that all methods (other than those declared in java.lang.Object)
	// throw an exception that is a subclass of RemoteException.
	for (int ctr=0; ctr<methods.length; ctr++) {
	    Method method = methods[ctr] ;
	    if (method.getDeclaringClass() != Object.class)
		if (!throwsRemote( method ))
		    return false ;
	}	

	return true ;
    }

    private static boolean throwsRemote( Method method ) 
    {
	Class[] exceptionTypes = method.getExceptionTypes() ;

	// Check that some exceptionType is a subclass of RemoteException
	for (int ctr=0; ctr<exceptionTypes.length; ctr++) {
	    Class exceptionType = exceptionTypes[ctr] ;
	    if (java.rmi.RemoteException.class.isAssignableFrom( exceptionType ))
		return true ;
	}

	return false ;
    }

    public interface ReaderWriter
    {
	Object read( InputStream is ) ;
    
	void write( OutputStream os, Object value ) ;
    }

    abstract static class ReaderWriterBase implements ReaderWriter 
    {
	String name ;

	public ReaderWriterBase( String name ) 
	{
	    this.name = name ;
	}

	public String toString()
	{
	    return "ReaderWriter[" + name + "]" ;
	}
    }

    private static ReaderWriter booleanRW = new ReaderWriterBase( "boolean" ) 
    {
	public Object read( InputStream is ) 
	{
	    boolean value = is.read_boolean() ;
	    return new Boolean( value ) ;
	}

	public void write( OutputStream os, Object value ) 
	{
	    Boolean val = (Boolean)value ;
	    os.write_boolean( val.booleanValue() ) ;
	}
    } ;

    private static ReaderWriter byteRW = new ReaderWriterBase( "byte" ) 
    {
	public Object read( InputStream is ) 
	{
	    byte value = is.read_octet() ;
	    return new Byte( value ) ;
	}

	public void write( OutputStream os, Object value ) 
	{
	    Byte val = (Byte)value ;
	    os.write_octet( val.byteValue() ) ;
	}
    } ;

    private static ReaderWriter charRW = new ReaderWriterBase( "char" ) 
    {
	public Object read( InputStream is ) 
	{
	    char value = is.read_wchar() ;
	    return new Character( value ) ;
	}
    
	public void write( OutputStream os, Object value ) 
	{
	    Character val = (Character)value ;
	    os.write_wchar( val.charValue() ) ;
	}
    } ;
    
    private static ReaderWriter shortRW = new ReaderWriterBase( "short" ) 
    {
	public Object read( InputStream is ) 
	{
	    short value = is.read_short() ;
	    return new Short( value ) ;
	}
    
	public void write( OutputStream os, Object value ) 
	{
	    Short val = (Short)value ;
	    os.write_short( val.shortValue() ) ;
	}
    } ;
    
    private static ReaderWriter intRW = new ReaderWriterBase( "int" ) 
    {
	public Object read( InputStream is ) 
	{
	    int value = is.read_long() ;
	    return new Integer( value ) ;
	}
    
	public void write( OutputStream os, Object value ) 
	{
	    Integer val = (Integer)value ;
	    os.write_long( val.intValue() ) ;
	}
    } ;
    
    private static ReaderWriter longRW = new ReaderWriterBase( "long" ) 
    {
	public Object read( InputStream is ) 
	{
	    long value = is.read_longlong() ;
	    return new Long( value ) ;
	}
    
	public void write( OutputStream os, Object value ) 
	{
	    Long val = (Long)value ;
	    os.write_longlong( val.longValue() ) ;
	}
    } ;
    
    private static ReaderWriter floatRW = new ReaderWriterBase( "float" ) 
    {
	public Object read( InputStream is ) 
	{
	    float value = is.read_float() ;
	    return new Float( value ) ;
	}
    
	public void write( OutputStream os, Object value ) 
	{
	    Float val = (Float)value ;
	    os.write_float( val.floatValue() ) ;
	}
    } ;
    
    private static ReaderWriter doubleRW = new ReaderWriterBase( "double" ) 
    {
	public Object read( InputStream is ) 
	{
	    double value = is.read_double() ;
	    return new Double( value ) ;
	}
    
	public void write( OutputStream os, Object value ) 
	{
	    Double val = (Double)value ;
	    os.write_double( val.doubleValue() ) ;
	}
    } ;
    
    private static ReaderWriter corbaObjectRW = new ReaderWriterBase( 
	"org.omg.CORBA.Object" ) 
    {
	public Object read( InputStream is ) 
	{
	    return is.read_Object() ;
	}
    
	public void write( OutputStream os, Object value ) 
	{
	    os.write_Object( (org.omg.CORBA.Object)value ) ;
	}
    } ;
    
    private static ReaderWriter anyRW = new ReaderWriterBase( "any" ) 
    {
	public Object read( InputStream is ) 
	{
	    return Util.readAny(is) ;
	}

	public void write( OutputStream os, Object value )
	{
	    Util.writeAny( os, value ) ;
	}
    } ;

    private static ReaderWriter abstractInterfaceRW = new ReaderWriterBase( 
	"abstract_interface"  )
    {
	public Object read( InputStream is ) 
	{
	    return is.read_abstract_interface() ;
	}

	public void write( OutputStream os, Object value )
	{
	    Util.writeAbstractObject( os, value ) ;
	}
    } ;

 
    public static ReaderWriter makeReaderWriter( final Class cls ) 
    {
	if (cls.equals( boolean.class ))
	    return booleanRW ;
	else if (cls.equals( byte.class ))
	    return byteRW ;
	else if (cls.equals( char.class ))
	    return charRW ;
	else if (cls.equals( short.class ))
	    return shortRW ;
	else if (cls.equals( int.class ))
	    return intRW ;
	else if (cls.equals( long.class ))
	    return longRW ;
	else if (cls.equals( float.class ))
	    return floatRW ;
	else if (cls.equals( double.class ))
	    return doubleRW ;
	else if (java.rmi.Remote.class.isAssignableFrom( cls ))
	    return new ReaderWriterBase( "remote(" + cls.getName() + ")" ) 
	    {
		public Object read( InputStream is ) 
		{
		    return PortableRemoteObject.narrow( is.read_Object(), 
			cls ) ;
		}

		public void write( OutputStream os, Object value ) 
		{
		    Util.writeRemoteObject( os, value ) ;
		}
	    } ;
	else if (cls.equals(org.omg.CORBA.Object.class))
	    return corbaObjectRW ;
	else if (org.omg.CORBA.Object.class.isAssignableFrom( cls ))
	    return new ReaderWriterBase( "org.omg.CORBA.Object(" + 
		cls.getName() + ")" ) 
	    {
		public Object read( InputStream is ) 
		{
		    return is.read_Object(cls) ;
		}
	    
		public void write( OutputStream os, Object value ) 
		{
		    os.write_Object( (org.omg.CORBA.Object)value ) ;
		}
	    } ;
	else if (isAnyClass(cls))
	    return anyRW ;
	else if (isAbstractInterface(cls))
	    return abstractInterfaceRW ;

	// For anything else, just read it as a value type.
	return new ReaderWriterBase( "value(" + cls.getName() + ")" ) 
	{
	    public Object read( InputStream is ) 
	    {
		return is.read_value(cls) ;
	    }

	    public void write( OutputStream os, Object value )
	    {
		os.write_value( (Serializable)value, cls ) ;
	    }
	} ;
    }

    public DynamicMethodMarshallerImpl( Method method )
    {
	this.method = method ;
	ehandler = new ExceptionHandlerImpl( method.getExceptionTypes() ) ;
	needsArgumentCopy = false ;
		
	Class[] argTypes = method.getParameterTypes() ;
	hasArguments = argTypes.length > 0 ;
	if (hasArguments) {
	    argRWs = new ReaderWriter[ argTypes.length ] ;
	    for (int ctr=0; ctr<argTypes.length; ctr++ ) {
		// This could be further optimized to avoid
		// copying if argTypes contains at most one
		// immutable object type.
		if (!argTypes[ctr].isPrimitive())
		    needsArgumentCopy = true ;
		argRWs[ctr] = makeReaderWriter( argTypes[ctr] ) ;
	    }
	}

	Class resultType = method.getReturnType() ;
	needsResultCopy = false ;
	hasVoidResult = resultType.equals( void.class ) ;
	if (!hasVoidResult) {
	    needsResultCopy = !resultType.isPrimitive() ;
	    resultRW = makeReaderWriter( resultType ) ;
	}
    }

    public Method getMethod() 
    {
	return method ;
    }

    public Object[] copyArguments( Object[] args, 
	ORB orb ) throws RemoteException
    {
	if (needsArgumentCopy)
	    return Util.copyObjects( args, orb ) ;
	else
	    return args ;
    }

    public Object[] readArguments( InputStream is ) 
    {
	Object[] result = null ;

	if (hasArguments) {
	    result = new Object[ argRWs.length ] ;
	    for (int ctr=0; ctr<argRWs.length; ctr++ ) 
		result[ctr] = argRWs[ctr].read( is ) ;
	}

	return result ;
    }

    public void writeArguments( OutputStream os, Object[] args ) 
    {
	if (hasArguments) {
	    if (args.length != argRWs.length)
		throw new IllegalArgumentException( "Expected " + argRWs.length +
		    " arguments, but got " + args.length + " arguments." ) ;

	    for (int ctr=0; ctr<argRWs.length; ctr++ ) 
		argRWs[ctr].write( os, args[ctr] ) ;
	}
    }

    public Object copyResult( Object result, ORB orb ) throws RemoteException
    {
	if (needsResultCopy)
	    return Util.copyObject( result, orb ) ;
	else 
	    return result ;
    }

    public Object readResult( InputStream is ) 
    {
	if (hasVoidResult)
	    return null ;
	else
	    return resultRW.read( is ) ;
    }

    public void writeResult( OutputStream os, Object result ) 
    {
	if (!hasVoidResult)
	    resultRW.write( os, result ) ;
    }

    public boolean isDeclaredException( Throwable thr )
    {
	return ehandler.isDeclaredException( thr.getClass() ) ;
    }

    public void writeException( OutputStream os, Exception ex ) 
    {
	ehandler.writeException( os, ex ) ;
    }

    public Exception readException( ApplicationException ae ) 
    {
	return ehandler.readException( ae ) ;
    }
}
