//$Id: Reflections.java 9572 2008-11-15 22:33:10Z shane.bryzak@jboss.com $
package org.jboss.seam.util;

import java.beans.Introspector;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;

public class Reflections
{
   
   public static Object invoke(Method method, Object target, Object... args) throws Exception
   {
      try
      {
         return method.invoke( target, args );
      }
      catch (IllegalArgumentException iae)
      {
         String message = "Could not invoke method by reflection: " + toString(method);
         if (args!=null && args.length>0) 
         {
            message += " with parameters: (" + Strings.toClassNameString(", ", args) + ')';
         }
         message += " on: " + target.getClass().getName();
         throw new IllegalArgumentException(message, iae);
      }
      catch (InvocationTargetException ite)
      {
         if ( ite.getCause() instanceof Exception )
         {
            throw (Exception) ite.getCause();
         }
         else
         {
            throw ite;
         }
      }
   }
   
   public static Object get(Field field, Object target) throws Exception
   {
      boolean accessible = field.isAccessible();
      try
      {
         field.setAccessible(true);
         return field.get(target);
      }
      catch (IllegalArgumentException iae)
      {
         String message = "Could not get field value by reflection: " + toString(field) + 
            " on: " + target.getClass().getName();
         throw new IllegalArgumentException(message, iae);
      }
      finally
      {
         field.setAccessible(accessible);
      }
   }
   
   public static void set(Field field, Object target, Object value) throws Exception
   {
      try
      {
         field.set(target, value);
      }
      catch (IllegalArgumentException iae)
      {
         // target may be null if field is static so use field.getDeclaringClass() instead
         String message = "Could not set field value by reflection: " + toString(field) +
            " on: " + field.getDeclaringClass().getName();
         if (value==null)
         {
            message += " with null value";
         }
         else
         {
            message += " with value: " + value.getClass();
         }
         throw new IllegalArgumentException(message, iae);
      }
   }
   
   public static Object getAndWrap(Field field, Object target)
   {
      boolean accessible = field.isAccessible();
      try
      {
         field.setAccessible(true);
         return get(field, target);
      }
      catch (Exception e)
      {
         if (e instanceof RuntimeException)
         {
            throw (RuntimeException) e;
         }
         else
         {
            throw new IllegalArgumentException("exception setting: " + field.getName(), e);
         }
      }
      finally
      {
         field.setAccessible(accessible);
      }
   }
   
   public static void setAndWrap(Field field, Object target, Object value)
   {
      boolean accessible = field.isAccessible();
      try
      {
         field.setAccessible(true);
         set(field, target, value);
      }
      catch (Exception e)
      {
         if (e instanceof RuntimeException)
         {
            throw (RuntimeException) e;
         }
         else
         {
            throw new IllegalArgumentException("exception setting: " + field.getName(), e);
         }
      }
      finally
      {
         field.setAccessible(accessible);
      }
   }
   
   public static Object invokeAndWrap(Method method, Object target, Object... args)
   {
      try
      {
         return invoke(method, target, args);
      }
      catch (Exception e)
      {
         if (e instanceof RuntimeException)
         {
            throw (RuntimeException) e;
         }
         else
         {
            throw new RuntimeException("exception invoking: " + method.getName(), e);
         }
      }
   }
   
   public static String toString(Method method)
   {
      return Strings.unqualify( method.getDeclaringClass().getName() ) + 
            '.' + 
            method.getName() + 
            '(' + 
            Strings.toString( ", ", method.getParameterTypes() ) + 
            ')';
   }
   
   public static String toString(Member member)
   {
      return Strings.unqualify( member.getDeclaringClass().getName() ) + 
            '.' + 
            member.getName();
   }
   
   public static Class classForName(String name) throws ClassNotFoundException
   {
      try 
      {
         return Thread.currentThread().getContextClassLoader().loadClass(name);
      }
      catch (Exception e)
      {
         return Class.forName(name);
      }
   }
   
   /**
    * Return's true if the class can be loaded using Reflections.classForName()
    */
   public static boolean isClassAvailable(String name)
   {
      try 
      {
         classForName(name);
      }
      catch (ClassNotFoundException e) {
         return false;
      }
      return true;
   }

   public static Class getCollectionElementType(Type collectionType)
   {
      if ( !(collectionType instanceof ParameterizedType) )
      {
         throw new IllegalArgumentException("collection type not parameterized");
      }
      Type[] typeArguments = ( (ParameterizedType) collectionType ).getActualTypeArguments();
      if (typeArguments.length==0)
      {
         throw new IllegalArgumentException("no type arguments for collection type");
      }
      Type typeArgument = typeArguments.length==1 ? typeArguments[0] : typeArguments[1]; //handle Maps
      if (typeArgument instanceof ParameterizedType)
      {
         typeArgument = ((ParameterizedType) typeArgument).getRawType();
      }
      if ( !(typeArgument instanceof Class) )
      {
         throw new IllegalArgumentException("type argument not a class");
      }
      return (Class) typeArgument;
   }
   
   public static Class getMapKeyType(Type collectionType)
   {
      if ( !(collectionType instanceof ParameterizedType) )
      {
         throw new IllegalArgumentException("collection type not parameterized");
      }
      Type[] typeArguments = ( (ParameterizedType) collectionType ).getActualTypeArguments();
      if (typeArguments.length==0)
      {
         throw new IllegalArgumentException("no type arguments for collection type");
      }
      Type typeArgument = typeArguments[0];
      if ( !(typeArgument instanceof Class) )
      {
         throw new IllegalArgumentException("type argument not a class");
      }
      return (Class) typeArgument;
   }
   
   public static Method getSetterMethod(Class clazz, String name)
   {
      Method[] methods = clazz.getMethods();
      for (Method method: methods)
      {
         String methodName = method.getName();
         if ( methodName.startsWith("set") && method.getParameterTypes().length==1 )
         {
            if ( Introspector.decapitalize( methodName.substring(3) ).equals(name) )
            {
               return method;
            }
         }
      }
      throw new IllegalArgumentException("no such setter method: " + clazz.getName() + '.' + name);
   }
   
   public static Method getGetterMethod(Class clazz, String name)
   {
      Method[] methods = clazz.getMethods();
      for (Method method: methods)
      {
         String methodName = method.getName();
         if ( method.getParameterTypes().length==0 )
         {
            if ( methodName.startsWith("get") )
            {
               if ( Introspector.decapitalize( methodName.substring(3) ).equals(name) )
               {
                  return method;
               }
            }
            else if ( methodName.startsWith("is") )
            {
               if ( Introspector.decapitalize( methodName.substring(2) ).equals(name) )
               {
                  return method;
               }
            }
         }
      }
      throw new IllegalArgumentException("no such getter method: " + clazz.getName() + '.' + name);
   }
   
   /**
    * Get all the getter methods annotated with the given annotation. Returns an empty list if
    * none are found
    */
   public static List<Method> getGetterMethods(Class clazz, Class annotation) 
   {
      List<Method> methods = new ArrayList<Method>();
      for (Method method : clazz.getMethods())
      {
         if (method.isAnnotationPresent(annotation))
         {
            methods.add(method);
         }
      }
      return methods;
   }
   
   public static Field getField(Class clazz, String name)
   {
      for ( Class superClass = clazz; superClass!=Object.class; superClass=superClass.getSuperclass() )
      {
         try
         {
            return superClass.getDeclaredField(name);
         }
         catch (NoSuchFieldException nsfe) {}
      }
      throw new IllegalArgumentException("no such field: " + clazz.getName() + '.' + name);
   }
   
   /**
    * Get all the fields which are annotated with the given annotation. Returns an empty list
    * if none are found
    */
   public static List<Field> getFields(Class clazz, Class annotation)
   {
      List<Field> fields = new ArrayList<Field>();
      for (Class superClass = clazz; superClass!=Object.class; superClass=superClass.getSuperclass())
      {
         for (Field field : superClass.getDeclaredFields())
         {
            if (field.isAnnotationPresent(annotation))
            {
               fields.add(field);
            }
         }
      }
      return fields;
   }

   public static Method getMethod(Annotation annotation, String name)
   {
      try
      {
         return annotation.annotationType().getMethod(name);
      }
      catch (NoSuchMethodException nsme)
      {
         return null;
      }
   }
   
   public static Method getMethod(Class clazz, String name)
   {
      for ( Class superClass = clazz; superClass!=Object.class; superClass=superClass.getSuperclass() )
      {
         try
         {
            return superClass.getDeclaredMethod(name);
         }
         catch (NoSuchMethodException nsme) {}
      }
      throw new IllegalArgumentException("no such method: " + clazz.getName() + '.' + name);
   }
   
   /**
    * Check to see if clazz is an instance of name
    */
   public static boolean isInstanceOf(Class clazz, String name)
   {
      if (name == null)
      {
         throw new IllegalArgumentException("name cannot be null");
      }
      for (Class c = clazz; c != Object.class; c = c.getSuperclass())
      {
         if (instanceOf(c, name))
         {
            return true;
         }
      }
      return false;
   }
   
   private static boolean instanceOf(Class clazz, String name)
   {  
      if (name.equals(clazz.getName()))
      {
         return true;
      }
      else
      {
         boolean found = false;
         Class[] interfaces = clazz.getInterfaces();
         for (int i = 0; i < interfaces.length && !found; i++)
         {
            found = instanceOf(interfaces[i], name);
         }
         return found;
      }
      
   }

}
