/*
 * ReflectUtil.java  $Revision: 1.19 $  $Date: 2007/10/03 04:39:39 $
 *
 * Copyright (C) 2007 Meldcraft Corporation
 *
 * This file is part of Scaffold.
 * 
 * Scaffold is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Scaffold is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with Scaffold.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.meldcraft.util;

import java.awt.Font;
import java.awt.Image;
import java.awt.MediaTracker;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Map;
import java.util.WeakHashMap;

import javax.swing.Icon;
import javax.swing.ImageIcon;

/**
 * @version $Revision: 1.19 $ $Date: 2007/10/03 04:39:39 $
 * @author Rob Richards
 */
public class ReflectUtil
{
// Static variables
    private static Map  sConverterMap;
    static
    {
        sConverterMap = new WeakHashMap();

        try
        {
            Map config = LayeredProperties.getProperties(ReflectUtil.class);
            if (config != null)
            {
                config = PropertiesHelper.getMap(config, "converters");
                if (config != null)
                {
                    config.remove(PropertiesHelper.ORDERED_KEYS);
                }
            }

            if (config != null)
            {
                Object[] keys = config.keySet().toArray();
                for (int i=0 ; i < keys.length ; i++)
                {
                    String key = (String)keys[i];
                    String val = (String)config.get(key);
                    try
                    {
                        Class ckey = Class.forName(key);
                        Class cval = Class.forName(val);
                        config.put(ckey, (Converter)cval.getConstructor(null).newInstance(null));
                    }
                    catch (IllegalArgumentException e)
                    {
                        // ignored
                    }
                    catch (SecurityException e)
                    {
                        // ignored
                    }
                    catch (InstantiationException e)
                    {
                        // ignored
                    }
                    catch (IllegalAccessException e)
                    {
                        // ignored
                    }
                    catch (InvocationTargetException e)
                    {
                        // ignored
                    }
                    catch (NoSuchMethodException e)
                    {
                        // ignored
                    }
                    catch (ClassNotFoundException e)
                    {
                        // ignored
                    }
                    catch (ClassCastException e)
                    {
                        // ignored
                    }
                }
                sConverterMap.putAll(config);
            }
        }
        catch (IOException e)
        {
            // whoops
        }
    }

// Member variables
// Constructors and initialization
// Public methods
    /** Returns the implmentation title of the package for obj. */
    public static String getComponentTitle(Object obj)
    {
        String val = null;
        if (obj != null)
        {
            Package pkg = obj.getClass().getPackage();
            if (pkg != null)
            {
                val = pkg.getImplementationTitle();
            }
        }
        return val;
    }

    /** Returns the implmentation version of the package for obj. */
    public static String getComponentVersion(Object obj)
    {
        String val = null;
        if (obj != null)
        {
            Package pkg = obj.getClass().getPackage();
            if (pkg != null)
            {
                val = pkg.getImplementationVersion();
            }
        }
        return val;
    }

    /** Returns the implmentation vendor of the package for obj. */
    public static String getComponentVendor(Object obj)
    {
        String val = null;
        if (obj != null)
        {
            Package pkg = obj.getClass().getPackage();
            if (pkg != null)
            {
                val = pkg.getImplementationVendor();
            }
        }
        return val;
    }

    /** Returns the name of the class (minus the package prefix). */
    public static String getSimpleClassName(Class clas)
    {
        String name = clas.getName();
        return getSimpleClassName(name);
    }

    /** Returns the name of the class (minus the package prefix). */
    public static String getSimpleClassName(String name)
    {
        name = name.substring(name.lastIndexOf('.')+1, name.length());
        return name;
    }

    /** Creates a new Object of type clazz using a single-arg constructor. */
    public static Object createInstance(Class clazz, Class argType, Object arg)
        throws NoSuchMethodException, InstantiationException,
               IllegalAccessException, InvocationTargetException
    {
        if (argType == null)
        {
            return createInstance(clazz, (Class[])null, (Object[])null);
        }
        else
        {
            return createInstance(clazz, new Class[]{argType}, new Object[]{arg});
        }
    }

    /** Creates a new Object of type clazz using a multiple-arg constructor. */
    public static Object createInstance(Class clazz, Class[] argTypes, Object[] args)
        throws NoSuchMethodException, InstantiationException,
               IllegalAccessException, InvocationTargetException
    {
        Object ret;
        ret = clazz.getConstructor(argTypes).newInstance(args);
        return ret;
    }

    /** Creates a new Object of type clazz using a multiple-arg constructor. */
    public static Object createInstance(Class clazz, Object[] args)
        throws NoSuchMethodException, InstantiationException,
               IllegalAccessException, InvocationTargetException
    {
        Object ret = null;
        if (args == null)
        {
            ret = clazz.getConstructor(null).newInstance(null);
        }
        else
        {
            Constructor[] constructors = clazz.getConstructors();
            if (constructors != null)
            {
                for (int i=0 ; i < constructors.length ; i++)
                {
                    //ret = clazz.getConstructor(argTypes).newInstance(args);
                    Constructor con = constructors[i];
                    Class[] cargs = con.getParameterTypes();
                    if (cargs.length != args.length)
                    {
                        continue;
                    }
                    else if (cargs.length == 1)
                    {
                        Class type = cargs[0];
                        if (cargs[0].isPrimitive())
                        {
                            if (cargs[0] == Boolean.TYPE)
                            {
                                type = Boolean.class;
                            }
                            else if (cargs[0] == Character.TYPE)
                            {
                                type = Character.class;
                            }
                            else if (cargs[0] == Byte.TYPE)
                            {
                                type = Byte.class;
                            }
                            else if (cargs[0] == Short.TYPE)
                            {
                                type = Short.class;
                            }
                            else if (cargs[0] == Integer.TYPE)
                            {
                                type = Integer.class;
                            }
                            else if (cargs[0] == Long.TYPE)
                            {
                                type = Long.class;
                            }
                            else if (cargs[0] == Float.TYPE)
                            {
                                type = Float.class;
                            }
                            else if (cargs[0] == Double.TYPE)
                            {
                                type = Double.class;
                            }
                            else if (cargs[0] == Void.TYPE)
                            {
                                type = Void.class;
                            }
                        }

                        if (type.equals(clazz))
                        {
                            // copy constructor - will cause recursion via 
                            // matchArgs()->convert()->createInstance()
                            continue;
                        }
                    }

                    Object[] oargs = new Object[args.length];
                    System.arraycopy(args,0, oargs,0,oargs.length);
                    boolean match = matchArgs(cargs, oargs);

                    if (match)
                    {
                        ret = con.newInstance(oargs);
                        break;
                    }
                }
            }

            if (ret == null)
            {
                StringBuffer buf = new StringBuffer(clazz.getName()).append('(');
                if (args != null)
                {
                    for (int i=0 ; i < args.length ; i++)
                    {
                        if (i > 0)
                        {
                            buf.append(',');
                        }
                        buf.append(args[i].getClass().getName()); 
                    }
                }
                buf.append(')');
                throw new NoSuchMethodException(buf.toString());
            }
        }

        return ret;
    }

    /** Creates a new Object of type clazz using a no-arg constructor. */
    public static Object createInstance(Class clazz)
        throws NoSuchMethodException, InstantiationException,
               IllegalAccessException, InvocationTargetException
    {
        return createInstance(clazz, (Class)null, (Object)null);
    }

    /** Creates a new Object of type clazz using a no-arg constructor. */
    public static Object createInstance(String className)
        throws ClassNotFoundException, NoSuchMethodException,
               InstantiationException, IllegalAccessException,
               InvocationTargetException
    {
        return createInstance(className, (Class)null, (Object)null);
    }

    /** Creates a new Object of type clazz using a single-arg constructor. */
    public static Object createInstance(String className, Class argType, Object arg)
        throws ClassNotFoundException, NoSuchMethodException,
               InstantiationException, IllegalAccessException,
               InvocationTargetException
    {
        Class clazz = Class.forName(className);
        return createInstance(clazz, argType, arg);
    }

    /** Creates a new Object of type clazz using a multi-arg constructor. */
    public static Object createInstance(String className, Class[] argTypes, Object[] args)
        throws ClassNotFoundException, NoSuchMethodException,
               InstantiationException, IllegalAccessException,
               InvocationTargetException
    {
        Class clazz = Class.forName(className);
        return createInstance(clazz, argTypes, args);
    }

    /** Invokes methodName on reflectObject. */
    public static Object invokeMethod(Object reflectObject, String methodName)
        throws NoSuchMethodException, IllegalAccessException,
               InvocationTargetException
    {
        return invokeMethod(reflectObject, methodName, (Object)null);
    }

    /** Invokes methodName on reflectObject using a single argument. */
    public static Object invokeMethod(Object reflectObject, String methodName,
                                      Object arg)
        throws NoSuchMethodException, IllegalAccessException,
               InvocationTargetException
    {
        if (arg == null)
        {
            return invokeMethod(reflectObject, methodName, null);
        }
        else
        {
            return invokeMethod(reflectObject, methodName, new Object[]{arg});
        }
    }

    /** Invokes methodName on reflectObject using multiple arguments. */
    public static Object invokeMethod(Object reflectObject, String methodName,
                                    Object[] args)
        throws NoSuchMethodException, IllegalAccessException,
               InvocationTargetException
    {
        if (args == null)
        {
            args = new Object[0];
        }

        Method method = findMethod(reflectObject, methodName, args);
        if (method != null)
        {
            return method.invoke(reflectObject, args);
        }
        else
        {
            StringBuffer message = new StringBuffer();
            message.append("No method ")
                   .append(reflectObject.getClass().getName())
                   .append('.')
                   .append(methodName)
                   .append('(');
            for (int i=0 ; i < args.length ; i++)
            {
                if (i > 0)
                {
                    if (args[i] == null)
                    {
                        message.append(", null");
                    }
                    else
                    {
                        message.append(", ").append(args[i].getClass().getName());
                    }
                }
                else
                {
                    String val = null;
                    if (args[i] != null)
                    {
                        val = args[i].getClass().getName();
                    }
                    message.append(val);
                }
            }
            message.append(')');

            throw new NoSuchMethodException(message.toString());
        }
    }

    public static Object getFieldValue(Object target, String property)
        throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException
    {
        Class clas = (target instanceof Class) ? (Class)target : target.getClass();
        Field field = clas.getField(property);
        return field.get(target);
    }

    public static void setFieldValue(Object target, String property, Object value)
        throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException, NoSuchMethodException, InstantiationException, InvocationTargetException
    {
        Object val = value;
        Field field = target.getClass().getField(property);
        Class type = field.getType();
        if (!type.isAssignableFrom(val.getClass()))
        {
            val = convert(val, type);
        }

        field.set(target, val);
    }

    public static Object convert(Object object, Class toClass)
        throws SecurityException, NoSuchMethodException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException
    {
        Object ret = null;

        Converter converter = (Converter)sConverterMap.get(toClass);
        if (converter != null)
        {
            Object conv = converter.convert(object, toClass);
            if (conv == null)
            {
                throw new RuntimeException("Could not convert value");
            }
            ret = conv;
        }
        else
        {
            // find a constructor
            try
            {
                if (toClass == String.class)
                {
                    ret = String.valueOf(object);
                }
                else
                {
                    Object[] io = {object};
                    ret = createInstance(toClass, io);
                }
            }
            catch (NoSuchMethodException e)
            {
                if (object instanceof String)
                {
                    String[] args = PropertiesHelper.getStringArray((String)object);
                    if (toClass.isAssignableFrom(String[].class))
                    {
                        ret = args;
                    }
                    else if ((args != null) && (args.length > 1))
                    {
                        ret = createInstance(toClass, args);
                    }
                }

                if (ret == null)
                {
                    throw e;
                }
            }
        }

        return ret;
    }

    public static Method[] findMethods(Object reflectObject, String methodName)
    {
        Method[] methods = null;
        if (reflectObject instanceof Class)
        {   
            methods = ((Class)reflectObject).getMethods();
        }
        else
        {
            methods = reflectObject.getClass().getMethods();
        }

        if ((methods != null) && (methods.length > 0))
        {
            ArrayList list = new ArrayList();
            for (int i=0 ; i < methods.length ; i++)
            {
                if (methods[i].getName().equals(methodName))
                {
                    list.add(methods[i]);
                }
            }

            methods = (list.size() > 0) ? (Method[])list.toArray(new Method[list.size()]) : null;
        }

        return methods;
    }

// Protected methods
    protected static Method findMethod(Object reflectObject,
                                       String methodName,
                                       Object[] args)
    {
        Method retMethod = null;

        Method[] methods = null;
        if (reflectObject instanceof Class)
        {
            methods = ((Class)reflectObject).getDeclaredMethods();
        }
        else
        {
            methods = reflectObject.getClass().getDeclaredMethods();
        }

        if (methods != null)
        {
            retMethod = findMethod(reflectObject, methodName, methods, args);
        }

        if (retMethod == null)
        {
            if (reflectObject instanceof Class)
            {   
                methods = ((Class)reflectObject).getMethods();
            }
            else
            {
                methods = reflectObject.getClass().getMethods();
            }

            retMethod = findMethod(reflectObject, methodName, methods, args);
        }

        return retMethod;
    }


    protected static Method findMethod(Object reflectObject,
                                       String methodName,
                                       Method[] methods,
                                       Object[] args)
    {
        Method retMethod = null;

        for (int i=0 ; i < methods.length ; i++)
        {
            if (methods[i].getName().equals(methodName))
            {
                Class[] cargs = methods[i].getParameterTypes();
                if (cargs.length != args.length)
                {
                    continue;
                }

                Object[] oargs = new Object[args.length];
                System.arraycopy(args,0, oargs,0,oargs.length);
                boolean match = matchArgs(cargs, oargs);

                if (match)
                {
                    System.arraycopy(oargs,0, args,0,args.length);
                    retMethod = methods[i];
                    break;
                }
            }
        }

        return retMethod;
    }

//  Private methods
    private static boolean matchArgs(Class[] cargs, Object[] oargs)
    {
        boolean match = true;
        for (int j=0 ; j < cargs.length ; j++)
        {
            if (cargs[j].isPrimitive())
            {
                if (cargs[j] == Boolean.TYPE)
                {
                    cargs[j] = Boolean.class;
                }
                else if (cargs[j] == Character.TYPE)
                {
                    cargs[j] = Character.class;
                }
                else if (cargs[j] == Byte.TYPE)
                {
                    cargs[j] = Byte.class;
                }
                else if (cargs[j] == Short.TYPE)
                {
                    cargs[j] = Short.class;
                }
                else if (cargs[j] == Integer.TYPE)
                {
                    cargs[j] = Integer.class;
                }
                else if (cargs[j] == Long.TYPE)
                {
                    cargs[j] = Long.class;
                }
                else if (cargs[j] == Float.TYPE)
                {
                    cargs[j] = Float.class;
                }
                else if (cargs[j] == Double.TYPE)
                {
                    cargs[j] = Double.class;
                }
                else if (cargs[j] == Void.TYPE)
                {
                    cargs[j] = Void.class;
                }
            }

            if ((oargs[j] == null) || cargs[j].isAssignableFrom(oargs[j].getClass()))
            {
                // OK - leave argument in place
            }
            else
            {
                try
                {
                    oargs[j] = convert(oargs[j], cargs[j]);
                }
                catch (Exception e)
                {
                    match = false;
                    break;
                }
            }
        }

        return match;
    }

// Inner classes
    public static interface Converter
    {
        /**
         * Converts object into an instance of class.
         * Returns null if conversion not possible.
         * 
         * @param object
         * @param clas
         * @return
         */
        public Object convert(Object object, Class clas);
    }

    public static class ColorConverter implements Converter
    {
        public Object convert(Object object, Class clas)
        {
            Object ret = null;
            if (java.awt.Color.class.isAssignableFrom(clas))
            {
                ret = ColorUtil.getColor(String.valueOf(object));
            }
            return ret;
        }
    }

    public static class FontConverter implements Converter
    {
        public Object convert(Object object, Class clas)
        {
            Object ret = null;
            if (java.awt.Font.class.isAssignableFrom(clas))
            {
                if (object != null)
                {
                    ret = Font.decode(String.valueOf(object));
                }
            }
            return ret;
        }
    }

    public static class IconConverter implements Converter
    {
        public Object convert(Object object, Class clas)
        {
            Object ret = null;
            if (Icon.class.isAssignableFrom(clas))
            {
                if (object instanceof java.awt.Image)
                {
                    ret = new ImageIcon((Image)object);
                }
                else if (object != null)
                {
                    URL url = getClass().getClassLoader().getResource(String.valueOf(object));
                    ret = loadImageURL(url);
                }
            }
            return ret;
        }

        protected ImageIcon loadImageURL(URL url)
        {
            ImageIcon img = new ImageIcon(url);

            // wait for image to load completely
            int status = img.getImageLoadStatus();
            while ((status != MediaTracker.COMPLETE) &&
                   (status != MediaTracker.ABORTED) &&
                   (status != MediaTracker.ERRORED))
            {
                try{Thread.sleep(100);} catch(Exception e){}
                status = img.getImageLoadStatus();
            }

            return img;
        }
    }

    public static class IntegerConverter implements Converter
    {
        public Object convert(Object object, Class clas)
        {
            Object ret = null;
            if (Integer.class.isAssignableFrom(clas))
            {
                if (object instanceof Integer)
                {
                    ret = object;
                }
                else if (object instanceof String)
                {
                    String ostr = (String)object;
                    try
                    {
                        // integer as string
                        ret = Integer.valueOf(ostr);
                    }
                    catch (NumberFormatException e)
                    {
                        // field value
                        try
                        {
                            ret = getFieldValue(clas, ostr);
                        }
                        catch (NoSuchFieldException e1)
                        {
                            // constant from class
                            try
                            {
                                ret = getFieldValue(clas, ostr.toUpperCase());
                            }
                            catch (NoSuchFieldException e2)
                            {
                                // ignored
                            }
                            catch (SecurityException e2)
                            {
                                // ignored
                            }
                            catch (IllegalArgumentException e2)
                            {
                                // ignored
                            }
                            catch (IllegalAccessException e2)
                            {
                                // ignored
                            }
                        }
                        catch (SecurityException e1)
                        {
                            // ignored
                        }
                        catch (IllegalArgumentException e1)
                        {
                            // ignored
                        }
                        catch (IllegalAccessException e1)
                        {
                            // ignored
                        }
                    }
                }
            }
            return ret;
        }
    }
}
