/***************************************
 *                                     *
 *  JBoss: The OpenSource J2EE WebOS   *
 *                                     *
 *  Distributable under LGPL license.  *
 *  See terms of license at gnu.org.   *
 *                                     *
 ***************************************/

package org.jboss.iiop.rmi;

import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Iterator;

/**
 * This class contains a bunch of methods taken from 
 * org.jboss.verifier.strategy.AbstractVerifier. There they are declared
 * as instance methods. I copied them to this class and made them static here 
 * in order to call them as utility methods, without having to create a 
 * verifier instance, 
 *
 * @author  <a href="mailto:juha.lindfors@jboss.org">Juha Lindfors</a>
 * @author  Aaron Mulder  (ammulder@alumni.princeton.edu)
 * @author  Vinay Menon   (menonv@cpw.co.uk)
 * @author  <a href="mailto:reverbel@ime.usp.br">Francisco Reverbel</a>
 * @version $Revision: 1.2.4.2 $
 */
public class RmiIdlUtil {

   public static boolean hasLegalRMIIIOPArguments(Method method) {
      
      Class[] params = method.getParameterTypes();
      
      for (int i = 0; i < params.length; ++i)
         if (!isRMIIIOPType(params[i]))
            return false;
      
      return true;
   }
   
   public static boolean hasLegalRMIIIOPReturnType(Method method) {
      return isRMIIIOPType(method.getReturnType());
   }
   
   public static boolean hasLegalRMIIIOPExceptionTypes(Method method) {
      
      /*
       * All checked exception classes used in method declarations
       * (other than java.rmi.RemoteException) MUST be conforming
       * RMI/IDL exception types.
       *
       * Spec 28.2.3 (4)
       */
      Iterator it = Arrays.asList(method.getExceptionTypes()).iterator();
      
      while (it.hasNext()) {
         Class exception = (Class)it.next();
         
         if (!isRMIIDLExceptionType(exception))
            return false;
      }
      
      return true;
   }
   
   /*
    * checks if the method includes java.rmi.RemoteException or its
    * subclass in its throws clause.
    */
   public static boolean throwsRemoteException(Method method) {
      
      Class[] exception = method.getExceptionTypes();
      
      for (int i = 0; i < exception.length; ++i)
         if (java.rmi.RemoteException.class.isAssignableFrom(exception[i]))
            return true;
      
      return false;
   }
   
   /*
    * checks if a class's member (method, constructor or field) has a 'static'
    * modifier.
    */
   public static boolean isStatic(Member member) {
      return (Modifier.isStatic(member.getModifiers()));
   }
   
   /*
    * checks if the given class is declared as static (inner classes only)
    */
   public static boolean isStatic(Class c) {
      return (Modifier.isStatic(c.getModifiers()));
   }
   
   /*
    * checks if a class's member (method, constructor or field) has a 'final'
    * modifier.
    */
   public static boolean isFinal(Member member) {
      return (Modifier.isFinal(member.getModifiers()));
   }
   
   /*
    * checks if the given class is declared as final
    */
   public static boolean isFinal(Class c) {
      return (Modifier.isFinal(c.getModifiers()));
   }
   
   /*
    * checks if a class's memeber (method, constructor or field) has a 'public'
    * modifier.
    */
   public static boolean isPublic(Member member) {
      return (Modifier.isPublic(member.getModifiers()));
   }
   
   /*
    * checks if the given class is declared as public
    */
   public static boolean isPublic(Class c) {
      return (Modifier.isPublic(c.getModifiers()));
   }
   
   /**
    * Checks whether all the fields in the class are declared as public.
    */
   public static boolean isAllFieldsPublic(Class c) {
      try {
         Field list[] = c.getFields();
         for(int i=0; i<list.length; i++)
            if(!Modifier.isPublic(list[i].getModifiers()))
               return false;
      } catch(Exception e) {
         return false;
      }
      return true;
   }
   
   /*
    * checks if the given class is declared as abstract
    */
   public static boolean isAbstract(Class c) {
      return (Modifier.isAbstract(c.getModifiers()));
   }
   
   public static boolean isRMIIIOPType(Class type) {
      
      /*
       *  Java Language to IDL Mapping
       *  ftp://ftp.omg.org/pub/docs/ptc/99-03-09.pdf
       *
       *  A conforming RMI/IDL type is a Java type whose values
       *  may be transmitted across an RMI/IDL remote interface at
       *  run-time. A Java data type is a conforming RMI/IDL type
       *  if it is:
       *
       *  - one of the Java primitive types (see Primitive Types on page 28-2).
       *  - a conforming remote interface (as defined in RMI/IDL Remote Interfaces on page 28-2).
       *  - a conforming value type (as defined in RMI/IDL Value Types on page 28-4).
       *  - an array of conforming RMI/IDL types (see RMI/IDL Arrays on page 28-5).
       *  - a conforming exception type (see RMI/IDL Exception Types on page 28-5).
       *  - a conforming CORBA object reference type (see CORBA Object Reference Types on page 28-6).
       *  - a conforming IDL entity type see IDL Entity Types on page 28-6).
       */
      
      /*
       * Primitive types.
       *
       * Spec 28.2.2
       */
        if (type.isPrimitive())
           return true;
        
        /*
         * Conforming array.
         *
         * Spec 28.2.5
         */
        if (type.isArray())
           return isRMIIIOPType(type.getComponentType());
        
        /*
         * Conforming CORBA reference type
         *
         * Spec 28.2.7
         */
        if (org.omg.CORBA.Object.class.isAssignableFrom(type))
           return true;
        
        /*
         * Conforming IDL Entity type
         *
         * Spec 28.2.8
         */
        if (org.omg.CORBA.portable.IDLEntity.class.isAssignableFrom(type))
           return true;
        
        /*
         * Conforming remote interface.
         *
         * Spec 28.2.3
         */
        if (isRMIIDLRemoteInterface(type))
           return true;
        
        /*
         * Conforming exception.
         *
         * Spec 28.2.6
         */
        if (isRMIIDLExceptionType(type))
           return true;
        
        /*
         * Conforming value type.
         *
         * Spec 28.2.4
         */
        if (isRMIIDLValueType(type))
           return true;
        
        return false;
   }
   
   public static boolean isRMIIDLRemoteInterface(Class type) {
      
      /*
       * If does not implement java.rmi.Remote, cannot be valid RMI-IDL
       * remote interface.
       */
      if (!java.rmi.Remote.class.isAssignableFrom(type))
         return false;
      
      Iterator methodIterator = Arrays.asList(type.getMethods()).iterator();
      
      while (methodIterator.hasNext()) {
         Method m = (Method)methodIterator.next();
         
         /*
          * All methods in the interface MUST throw
          * java.rmi.RemoteException or its subclass.
          *
          * Spec 28.2.3 (2)
          */
         if (!throwsRemoteException(m)) {
            return false;
         }
         
         /*
          * All checked exception classes used in method declarations
          * (other than java.rmi.RemoteException) MUST be conforming
          * RMI/IDL exception types.
          *
          * Spec 28.2.3 (4)
          */
         Iterator it = Arrays.asList(m.getExceptionTypes()).iterator();
         
         while (it.hasNext()) {
            Class exception = (Class)it.next();
            
            if (!isRMIIDLExceptionType(exception))
               return false;
         }
      }
      
      /*
       * The constant values defined in the interface MUST be
       * compile-time types of RMI/IDL primitive types or String.
       *
       * Spec 28.2.3 (6)
       */
      Iterator fieldIterator = Arrays.asList(type.getFields()).iterator();
      
      while (fieldIterator.hasNext()) {
         
         Field f = (Field)fieldIterator.next();
         
         if (f.getType().isPrimitive())
            continue;
         
         if (f.getType().equals(java.lang.String.class))
            continue;
         
         return false;
      }
      
      return true;
   }
   
   public static boolean isAbstractInterface(Class type) {
      
      /*
       * It must be a Java interface.
       */
      if (!type.isInterface())
         return false;

      /*
       * It must not be the interface of a CORBA object.
       */
      if (org.omg.CORBA.Object.class.isAssignableFrom(type))
         return false;
      
      /*
       * It must not extend java.rmi.Remote directly or indirectly.
       */
      if (java.rmi.Remote.class.isAssignableFrom(type))
         return false;
      
      Iterator methodIterator = Arrays.asList(type.getMethods()).iterator();
      
      while (methodIterator.hasNext()) {
         Method m = (Method)methodIterator.next();
         
         /*
          * All methods MUST throw java.rmi.RemoteException or a subclass.
          */
         if (!throwsRemoteException(m)) {
            return false;
         }
         
      }
      
      return true;
   }
   
   public static boolean isRMIIDLExceptionType(Class type) {
      
      /*
       * A conforming RMI/IDL Exception class MUST be a checked
       * exception class and MUST be a valid RMI/IDL value type.
       *
       * Spec 28.2.6
       */
      if (!Throwable.class.isAssignableFrom(type))
         return false;
      
      if (Error.class.isAssignableFrom(type))
         return false;
      
      if (RuntimeException.class.isAssignableFrom(type))
         return false;
      
      if (!isRMIIDLValueType(type))
         return false;
      
      return true;
   }
   
   public static boolean isRMIIDLValueType(Class type) {
      
      /*
       * A value type MUST NOT either directly or indirectly implement the
       * java.rmi.Remote interface.
       *
       * Spec 28.2.4 (4)
       */
      if (java.rmi.Remote.class.isAssignableFrom(type))
         return false;
      
      
      /*
       * It cannot be a CORBA object.
       */
      if (org.omg.CORBA.Object.class.isAssignableFrom(type))
         return false;
      
      /*
       * If class is a non-static inner class then its containing class must
       * also be a conforming RMI/IDL value type.
       *
       * Spec 2.8.4 (3)
       */
      if (type.getDeclaringClass() != null && isStatic(type))
         if (!isRMIIDLValueType(type.getDeclaringClass()))
            return false;
      
      return true;
   }

   public static boolean isAbstractValueType(Class type) {
      
      if (!type.isInterface())
         return false;

      if (org.omg.CORBA.Object.class.isAssignableFrom(type))
         return false;
      
      boolean cannotBeRemote = false;
      boolean cannotBeAbstractInterface = false;

      if (java.rmi.Remote.class.isAssignableFrom(type)) {
         cannotBeAbstractInterface = true;
      }
      else {
         cannotBeRemote = true;
      }

      Iterator methodIterator = Arrays.asList(type.getMethods()).iterator();

      while (methodIterator.hasNext()) {
         Method m = (Method)methodIterator.next();
         
         if (!throwsRemoteException(m)) {
            cannotBeAbstractInterface = true;
            cannotBeRemote = true;
            break;
         }

         Iterator it = Arrays.asList(m.getExceptionTypes()).iterator();
         
         while (it.hasNext()) {
            Class exception = (Class)it.next();
            
            if (!isRMIIDLExceptionType(exception)) {
               cannotBeRemote = true;
               break;
            }
         }
      }
      
      if (!cannotBeRemote) {
         Iterator fieldIterator = Arrays.asList(type.getFields()).iterator();
      
         while (fieldIterator.hasNext()) {
            Field f = (Field)fieldIterator.next();
         
            if (f.getType().isPrimitive())
               continue;
            if (f.getType().equals(java.lang.String.class))
                continue;
            cannotBeRemote = true;
            break;
         }
      }
      return cannotBeRemote && cannotBeAbstractInterface;
   }
    
}
