/*
 * Copyright 2010-2015 Institut Pasteur.
 * 
 * This file is part of Icy.
 * 
 * Icy is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Icy 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 General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Icy. If not, see <http://www.gnu.org/licenses/>.
 */
package icy.util;

import icy.file.FileUtil;
import icy.plugin.PluginLoader;
import icy.system.IcyExceptionHandler;
import icy.system.SystemUtil;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Vector;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import sun.net.www.protocol.file.FileURLConnection;

/**
 * @author stephane
 */
public class ClassUtil
{
    /**
     * Return the current thread context class loader
     */
    public static ClassLoader getContextClassLoader()
    {
        return SystemUtil.getContextClassLoader();
    }

    /**
     * Return the system class loader
     */
    public static ClassLoader getSystemClassLoader()
    {
        return SystemUtil.getSystemClassLoader();
    }

    /**
     * Return the list of all loaded classes by the specified {@link ClassLoader}.<br>
     * Warning: this function is not safe and would not always work as expected.<br>
     * It can return <code>null</code> if an error occurred.
     */
    public static List<Class<?>> getLoadedClasses(ClassLoader cl)
    {
        try
        {
            final Vector classes = (Vector) ReflectionUtil.getFieldObject(cl, "classes", true);
            return new ArrayList<Class<?>>(classes);
        }
        catch (Exception e)
        {
            IcyExceptionHandler.showErrorMessage(e, false, true);
        }

        return null;
    }

    /**
     * @param primitiveName
     * @return The Java primitive type represented by the given name, or null if the given name does
     *         not represent a primitive type
     */
    public static Class<?> getPrimitiveType(String primitiveName)
    {
        if (primitiveName.equals("byte"))
            return byte.class;
        if (primitiveName.equals("short"))
            return short.class;
        if (primitiveName.equals("int"))
            return int.class;
        if (primitiveName.equals("long"))
            return long.class;
        if (primitiveName.equals("char"))
            return char.class;
        if (primitiveName.equals("float"))
            return float.class;
        if (primitiveName.equals("double"))
            return double.class;
        if (primitiveName.equals("boolean"))
            return boolean.class;
        if (primitiveName.equals("void"))
            return void.class;

        return null;
    }

    /**
     * Get Class object of specified class name.<br>
     * First search in Plugin Class loader then from the system class loader.<br>
     * Primitive type are accepted.
     * 
     * @throws ClassNotFoundException
     */
    public static Class<?> findClass(String className) throws ClassNotFoundException
    {
        try
        {
            // first try to load from Plugin class loader
            return PluginLoader.loadClass(className);
        }
        catch (ClassNotFoundException e1)
        {
            try
            {
                // then try to load from System class loader
                return ClassLoader.getSystemClassLoader().loadClass(className);
            }
            catch (ClassNotFoundException e2)
            {
                try
                {
                    // try forName style from Plugin class loader with initialization
                    return Class.forName(className, true, PluginLoader.getLoader());
                }
                catch (ClassNotFoundException e3)
                {
                    try
                    {
                        // try forName style from Plugin class loader without initialization
                        return Class.forName(className, false, PluginLoader.getLoader());
                    }
                    catch (ClassNotFoundException e4)
                    {
                        // try with primitive type...
                        final Class<?> result = getPrimitiveType(className);

                        if (result != null)
                            return result;

                        // last luck...
                        return Class.forName(className);
                    }
                }
            }
        }
    }

    /**
     * Transform the specified path in qualified name.<br>
     * <br>
     * ex : "document/class/loader.class" --> "document.class.loader.class" (unix)
     * "document\class\loader.class" --> "document.class.loader.class" (win)
     * 
     * @param path
     */
    public static String getQualifiedNameFromPath(String path)
    {
        return FileUtil.getGenericPath(path).replace(FileUtil.separatorChar, '.');
    }

    /**
     * Transform the specified qualified name in path.<br>
     * Be careful, this function do not handle the file extension.<br>
     * <br>
     * ex : "plugins.user.loader.test" --> "plugins/user/loader/test"
     */
    public static String getPathFromQualifiedName(String qualifiedName)
    {
        return qualifiedName.replace('.', FileUtil.separatorChar);
    }

    /**
     * Get package name<br>
     * ex : "plugin.test.myClass" --> "plugin.test"
     */
    public static String getPackageName(String className)
    {
        final int index = className.lastIndexOf('.');

        if (index != -1)
            return className.substring(0, index);

        return "";
    }

    /**
     * Get first package name<br>
     * ex : "plugin.test.myClass" --> "plugin"
     */
    public static String getFirstPackageName(String className)
    {
        final String packageName = getPackageName(className);
        final int index = packageName.lastIndexOf('.');

        if (index != -1)
            return packageName.substring(0, index);

        return packageName;
    }

    /**
     * Get the base class name<br>
     * ex : "plugin.myClass$InternClass$1" --> "plugin.myClass"
     */
    public static String getBaseClassName(String className)
    {
        // handle inner classes...
        final int lastDollar = className.indexOf('$');
        if (lastDollar > 0)
            return className.substring(0, lastDollar);

        return className;
    }

    /**
     * Get simple class name<br>
     * ex : "plugin.test.myClass$InternClass$1" --> "myClass$InternClass$1"
     */
    public static String getSimpleClassName(String className)
    {
        final int index = className.lastIndexOf('.');

        if (index != -1)
            return className.substring(index + 1);

        return className;
    }

    /**
     * Return true if clazz implements the specified interface
     */
    public static Class<?>[] getInterfaces(Class<?> c)
    {
        if (c == null)
            return new Class[0];

        return c.getInterfaces();
    }

    /**
     * Return true if class is abstract
     */
    public static boolean isAbstract(Class<?> c)
    {
        if (c == null)
            return false;

        return Modifier.isAbstract(c.getModifiers());
    }

    /**
     * Return true if class is public
     */
    public static boolean isPublic(Class<?> c)
    {
        if (c == null)
            return false;

        return Modifier.isPublic(c.getModifiers());
    }

    /**
     * Return true if class is private
     */
    public static boolean isPrivate(Class<?> c)
    {
        if (c == null)
            return false;

        return Modifier.isPrivate(c.getModifiers());
    }

    /**
     * Return true if clazz is the same class or extends baseClass
     */
    public static boolean isSubClass(Class<?> clazz, Class<?> baseClass)
    {
        if ((clazz == null) || (baseClass == null))
            return false;

        return baseClass.isAssignableFrom(clazz);
    }

    /**
     * This method returns all resources that are located in the package identified by the given
     * <code>packageName</code>.<br>
     * <b>WARNING:</b><br>
     * This is a relative expensive operation. Depending on your classpath multiple directories, JAR and WAR files may
     * need to be scanned.<br>
     * Original code written by Jorg Hohwiller for the m-m-m project (http://m-m-m.sf.net)
     * 
     * @param packageName
     *        is the name of the {@link Package} to scan (ex: "java.awt.metrics")
     * @param extension
     *        resource extension if we want to retrieve only a specific type of resource (ex: ".class")<br>
     *        Note that extension filtering is not case sensitive.
     * @param recursive
     *        if set to <code>true</code> files from sub packages/folder are also returned.
     * @param includeFolder
     *        if <code>true</code> folder entry are also returned
     * @param includeJar
     *        if <code>true</code> all sub JAR files are also scanned
     * @param includeHidden
     *        if <code>true</code> all hidden files (starting by '.' character) are also scanned
     * @return
     *         all files contained in this package represented in path format (ex: "java/awt/geom/Rectangle2D.class")
     */
    public static List<String> getResourcesInPackage(String packageName, String extension, boolean recursive,
            boolean includeFolder, boolean includeJar, boolean includeHidden) throws IOException
    {
        final List<String> result = new ArrayList<String>();

        getResourcesInPackage(packageName, extension, recursive, includeFolder, includeJar, includeHidden, result);

        return result;
    }

    /**
     * Internal use, see {@link #getResourcesInPackage(String, String, boolean)}
     */
    private static void getResourcesInPackage(String packageName, String extension, boolean recursive,
            boolean includeFolder, boolean includeJar, boolean includeHidden, List<String> result) throws IOException
    {
        final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        final String path = ClassUtil.getPathFromQualifiedName(packageName);
        final Enumeration<URL> urls = classLoader.getResources(path);
        final String ext = StringUtil.isEmpty(extension) ? "" : extension.toLowerCase();

        while (urls.hasMoreElements())
        {
            final URL packageUrl = urls.nextElement();
            final String urlPath = URLDecoder.decode(packageUrl.getFile(), "UTF-8");
            final String protocol = packageUrl.getProtocol().toLowerCase();

            if ("file".equals(protocol))
                getResourcesInPath(urlPath, packageName, recursive, includeFolder, includeJar, includeHidden, result);
            else if ("jar".equals(protocol))
            {
                final JarURLConnection connection = (JarURLConnection) packageUrl.openConnection();
                final JarFile jarFile = connection.getJarFile();
                final Enumeration<JarEntry> jarEntryEnumeration = jarFile.entries();
                final String pathWithPrefix = path + '/';
                final int prefixLength = path.length() + 1;

                while (jarEntryEnumeration.hasMoreElements())
                {
                    final JarEntry jarEntry = jarEntryEnumeration.nextElement();
                    String absoluteFileName = jarEntry.getName();

                    if (StringUtil.isEmpty(extension) || absoluteFileName.endsWith(ext))
                    {
                        if (absoluteFileName.startsWith("/"))
                            absoluteFileName = absoluteFileName.substring(1);

                        boolean accept = true;
                        if (absoluteFileName.startsWith(pathWithPrefix))
                        {
                            if (!recursive)
                            {
                                int index = absoluteFileName.indexOf('/', prefixLength);
                                if (index != -1)
                                    accept = false;
                            }

                            if (!includeFolder && jarEntry.isDirectory())
                                accept = false;

                            if (accept)
                                result.add(absoluteFileName);
                        }
                    }
                }

                jarFile.close();
            }
        }
    }

    /**
     * This method returns all resources that are located in the specified path.<br>
     * 
     * @param path
     *        path to scan.
     * @param recursive
     *        if <code>true</code> all sub folder are also scanned.
     * @param includeJar
     *        if <code>true</code> all JAR files are also scanned
     * @param includeHidden
     *        if <code>true</code> all hidden files (starting by '.' character) are also scanned
     * @return list of found resources.
     */
    public static List<String> getResourcesInPath(String path, boolean recursive, boolean includeFolder,
            boolean includeJar, boolean includeHidden)
    {
        final List<String> result = new ArrayList<String>();

        getResourcesInPath(path, ClassUtil.getQualifiedNameFromPath(path), recursive, includeFolder, includeJar,
                includeHidden, result);

        return result;
    }

    /**
     * This method returns all resources that are located in the specified path.<br>
     * 
     * @param path
     *        path to scan.
     * @param basePath
     *        path prefix
     * @param recursive
     *        if <code>true</code> all sub folder are also scanned.
     * @param includeJar
     *        if <code>true</code> all JAR files are also scanned
     * @param includeHidden
     *        if <code>true</code> all hidden files (starting by '.' character) are also scanned
     * @return set of found class.
     */
    public static List<String> getResourcesInPath(String path, String basePath, boolean recursive,
            boolean includeFolder, boolean includeJar, boolean includeHidden)
    {
        final List<String> result = new ArrayList<String>();

        getResourcesInPath(path, basePath, recursive, includeFolder, includeJar, includeHidden, result);

        return result;
    }

    /**
     * This method returns all resources that are located in the specified path.<br>
     * 
     * @param path
     *        path to scan.
     * @param basePath
     *        path prefix
     * @param recursive
     *        if <code>true</code> all sub folder are also scanned.
     * @param includeJar
     *        if <code>true</code> all JAR files are also scanned
     * @param includeHidden
     *        if <code>true</code> all hidden files (starting by '.' character) are also scanned
     * @param result
     *        result list
     */
    public static void getResourcesInPath(String path, String basePath, boolean recursive, boolean includeFolder,
            boolean includeJar, boolean includeHidden, List<String> result)
    {
        final File file = new File(path);
        final String qualifiedPath;

        if (StringUtil.isEmpty(basePath))
            qualifiedPath = "";
        else
            qualifiedPath = basePath + "/";

        if (file.isDirectory())
        {
            if (recursive)
                findResourcesRecursive(file, includeFolder, includeJar, includeHidden, result, qualifiedPath);
            else
            {
                for (File subFile : file.listFiles())
                    findResourceInFile(subFile, includeJar, includeHidden, result, qualifiedPath);
            }
        }
        else
            findResourceInFile(file, includeJar, includeHidden, result, qualifiedPath);
    }

    private static void findResourcesRecursive(File directory, boolean includeFolder, boolean includeJar,
            boolean includeHidden, List<String> result, String basePath)
    {
        for (File childFile : directory.listFiles())
        {
            final String childFilename = childFile.getName();

            // folder ?
            if (childFile.isDirectory())
            {
                if (!includeHidden && childFilename.startsWith("."))
                    continue;

                // include this folder entry
                if (includeFolder)
                    result.add(basePath + childFilename);

                // then search in sub folder
                findResourcesRecursive(childFile, includeJar, includeFolder, includeHidden, result, basePath
                        + childFilename + '/');
            }
            else
                findResourceInFile(childFile, includeJar, includeHidden, result, basePath);
        }
    }

    /**
     * Search for all classes in specified file
     */
    public static void findResourceInFile(File file, boolean includeJar, boolean includeHidden, List<String> result,
            String basePath)
    {
        final String shortName = file.getName();

        if (!includeHidden && shortName.startsWith("."))
            return;

        final String fileName = file.getPath();

        if (FileUtil.getFileExtension(fileName, false).toLowerCase().equals("jar"))
        {
            if (includeJar)
                JarUtil.getAllFiles(fileName, false, includeHidden, result);
        }
        else
            result.add(basePath + shortName);
    }

    /**
     * This method finds all classes that are located in the package identified by the given <code>packageName</code>.<br>
     * <b>ATTENTION:</b><br>
     * This is a relative expensive operation. Depending on your classpath multiple
     * directories,JAR-, and WAR-files may need to be scanned. <br>
     * 
     * @param packageName
     *        is the name of the {@link Package} to scan.
     * @param includeSubPackages
     *        - if <code>true</code> all sub-packages of the specified {@link Package} will be
     *        included in the search.
     * @return found classes set
     * @throws IOException
     *         if the operation failed with an I/O error.
     */
    public static Set<String> findClassNamesInPackage(String packageName, boolean includeSubPackages)
            throws IOException
    {
        final HashSet<String> classes = new HashSet<String>();

        findClassNamesInPackage(packageName, includeSubPackages, classes);

        return classes;
    }

    /**
     * This method finds all classes that are located in the package identified by the given <code>packageName</code>.<br>
     * <b>ATTENTION:</b><br>
     * This is a relative expensive operation. Depending on your classpath multiple
     * directories,JAR-, and WAR-files may need to be scanned. <br>
     * Original code written by Jorg Hohwiller for the m-m-m project (http://m-m-m.sf.net)
     * 
     * @param packageName
     *        is the name of the {@link Package} to scan.
     * @param includeSubPackages
     *        - if <code>true</code> all sub-packages of the specified {@link Package} will be
     *        included in the search.
     * @param classes
     *        save found classes here
     */
    public static void findClassNamesInPackage(String packageName, boolean includeSubPackages, Set<String> classes)
            throws IOException
    {
        final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        final String path = ClassUtil.getPathFromQualifiedName(packageName);
        final Enumeration<URL> urls = classLoader.getResources(path);

        while (urls.hasMoreElements())
        {
            final URL packageUrl = urls.nextElement();
            final String urlPath = URLDecoder.decode(packageUrl.getFile(), "UTF-8");
            final String protocol = packageUrl.getProtocol().toLowerCase();

            if ("file".equals(protocol))
                findClassNamesInPath(urlPath, packageName, includeSubPackages, classes);
            else if ("jar".equals(protocol))
            {
                final JarURLConnection connection = (JarURLConnection) packageUrl.openConnection();
                final JarFile jarFile = connection.getJarFile();
                final Enumeration<JarEntry> jarEntryEnumeration = jarFile.entries();
                final String pathWithPrefix = path + '/';
                final int prefixLength = path.length() + 1;

                while (jarEntryEnumeration.hasMoreElements())
                {
                    final JarEntry jarEntry = jarEntryEnumeration.nextElement();
                    String absoluteFileName = jarEntry.getName();

                    if (absoluteFileName.endsWith(".class"))
                    {
                        if (absoluteFileName.startsWith("/"))
                            absoluteFileName = absoluteFileName.substring(1);

                        boolean accept = true;
                        if (absoluteFileName.startsWith(pathWithPrefix))
                        {
                            String qualifiedName = absoluteFileName.replace('/', '.');

                            if (!includeSubPackages)
                            {
                                int index = absoluteFileName.indexOf('/', prefixLength);
                                if (index != -1)
                                    accept = false;
                            }

                            if (accept)
                            {
                                final String className = filenameToClassname(qualifiedName);
                                if (className != null)
                                    classes.add(className);
                            }
                        }
                    }
                }

                jarFile.close();
            }
        }
    }

    /**
     * This method finds all classes that are located in the specified directory.<br>
     * 
     * @param path
     *        path to scan.
     * @param includeSubDir
     *        if <code>true</code> all sub-directory are also scanned.
     * @return set of found class.
     */
    public static HashSet<String> findClassNamesInPath(String path, boolean includeSubDir)
    {
        return findClassNamesInPath(path, ClassUtil.getQualifiedNameFromPath(path), includeSubDir, true);
    }

    /**
     * This method finds all classes that are located in the specified directory.<br>
     * 
     * @param path
     *        path to scan.
     * @param includeSubDir
     *        if <code>true</code> all sub-directory are also scanned.
     * @param includeJar
     *        if <code>true</code> all JAR files are also scanned
     * @return set of found class.
     */
    public static HashSet<String> findClassNamesInPath(String path, boolean includeSubDir, boolean includeJar)
    {
        return findClassNamesInPath(path, ClassUtil.getQualifiedNameFromPath(path), includeSubDir, includeJar);
    }

    /**
     * This method finds all classes that are located in the specified directory.<br>
     * 
     * @param path
     *        path to scan.
     * @param packageName
     *        package name prefix
     * @param includeSubDir
     *        if <code>true</code> all sub-directory are also scanned.
     * @return set of found class.
     */
    public static HashSet<String> findClassNamesInPath(String path, String packageName, boolean includeSubDir)
    {
        final HashSet<String> classes = new HashSet<String>();

        findClassNamesInPath(path, packageName, includeSubDir, true, classes);

        return classes;
    }

    /**
     * This method finds all classes that are located in the specified directory.<br>
     * 
     * @param path
     *        path to scan.
     * @param packageName
     *        package name prefix
     * @param includeSubDir
     *        if <code>true</code> all sub-directory are also scanned.
     * @param includeJar
     *        if <code>true</code> all JAR files are also scanned
     * @return set of found class.
     */
    public static HashSet<String> findClassNamesInPath(String path, String packageName, boolean includeSubDir,
            boolean includeJar)
    {
        final HashSet<String> classes = new HashSet<String>();

        findClassNamesInPath(path, packageName, includeSubDir, includeJar, classes);

        return classes;
    }

    /**
     * This method finds all classes that are located in the specified directory.<br>
     * 
     * @param path
     *        path to scan.
     * @param packageName
     *        package name prefix
     * @param includeSubDir
     *        if <code>true</code> all sub-directory are also scanned.
     * @param classes
     *        save found classes here
     */
    public static void findClassNamesInPath(String path, String packageName, boolean includeSubDir, Set<String> classes)
    {
        findClassNamesInPath(path, packageName, includeSubDir, true, classes);
    }

    /**
     * This method finds all classes that are located in the specified directory.<br>
     * 
     * @param path
     *        path to scan.
     * @param packageName
     *        package name prefix
     * @param includeSubDir
     *        if <code>true</code> all sub-directory are also scanned.
     * @param includeJar
     *        if <code>true</code> all JAR files are also scanned
     * @param classes
     *        save found classes here
     */
    public static void findClassNamesInPath(String path, String packageName, boolean includeSubDir, boolean includeJar,
            Set<String> classes)
    {
        final File dir = new File(path);
        final String qualifiedName;

        if (StringUtil.isEmpty(packageName))
            qualifiedName = "";
        else
            qualifiedName = packageName + '.';

        if (dir.isDirectory())
        {
            if (includeSubDir)
                findClassNamesRecursive(dir, includeJar, classes, qualifiedName);
            else
                for (File file : dir.listFiles())
                    findClassNameInFile(file, includeJar, classes, qualifiedName);
        }
        else
            findClassNameInFile(dir, classes, qualifiedName);
    }

    private static void findClassNamesRecursive(File directory, boolean includeJar, Set<String> classSet,
            String qualifiedName)
    {
        for (File childFile : directory.listFiles())
        {
            final String childFilename = childFile.getName();

            // files or directories starting with "." aren't allowed
            if (!childFilename.startsWith("."))
            {
                if (childFile.isDirectory())
                    findClassNamesRecursive(childFile, includeJar, classSet, qualifiedName + childFilename + '.');
                else
                    findClassNameInFile(childFile, includeJar, classSet, qualifiedName);
            }
        }
    }

    /**
     * Search for all classes in specified file
     */
    public static void findClassNameInFile(File file, boolean includeJar, Set<String> classSet,
            String qualifiedNamePrefix)
    {
        final String fileName = file.getPath();
        if (FileUtil.getFileExtension(fileName, false).toLowerCase().equals("jar"))
        {
            if (includeJar)
                findClassNamesInJAR(fileName, classSet);
        }
        else
            addClassFileName(file.getName(), classSet, qualifiedNamePrefix);
    }

    /**
     * Search for all classes in specified file
     */
    public static void findClassNameInFile(File file, Set<String> classSet, String qualifiedNamePrefix)
    {
        findClassNameInFile(file, true, classSet, qualifiedNamePrefix);
    }

    /**
     * Search for all classes in JAR file
     */
    public static void findClassNamesInJAR(String fileName, Set<String> classSet)
    {
        final JarFile jarFile;

        try
        {
            jarFile = new JarFile(fileName);
        }
        catch (IOException e)
        {
            System.err.println("Cannot open " + fileName + ":");
            IcyExceptionHandler.showErrorMessage(e, false, true);
            return;
        }

        final Enumeration<JarEntry> entries = jarFile.entries();

        while (entries.hasMoreElements())
        {
            final JarEntry jarEntry = entries.nextElement();

            if (!jarEntry.isDirectory())
                addClassFileName(jarEntry.getName(), classSet, "");
        }

        try
        {
            jarFile.close();
        }
        catch (IOException e)
        {
            // ignore
        }
    }

    /**
     * Search for all classes in JAR file
     * 
     * @param fileName
     */
    public static Set<String> findClassNamesInJAR(String fileName)
    {
        final HashSet<String> result = new HashSet<String>();

        findClassNamesInJAR(fileName, result);

        return result;
    }

    private static void addClassFileName(String fileName, Set<String> classSet, String prefix)
    {
        final String simpleClassName = filenameToClassname(fileName);

        if (simpleClassName != null)
            classSet.add(prefix + simpleClassName);
    }

    /**
     * This method checks and transforms the filename of a potential {@link Class} given by <code>fileName</code>.
     * 
     * @param fileName
     *        is the filename.
     * @return the according Java {@link Class#getName() class-name} for the given <code>fileName</code> if it is a
     *         class-file that is no anonymous {@link Class}, else <code>null</code>.
     */
    public static String filenameToClassname(String fileName)
    {
        // class file ?
        if (fileName.toLowerCase().endsWith(".class"))
            // remove ".class" extension and fix classname
            return fixClassName(fileName.substring(0, fileName.length() - 6));

        return null;
    }

    /**
     * This method checks and transforms the filename of a potential {@link Class} given by <code>fileName</code>.<br>
     * Code written by Jorg Hohwiller for the m-m-m project (http://m-m-m.sf.net)
     * 
     * @param fileName
     *        is the filename.
     * @return the according Java {@link Class#getName() class-name} for the given <code>fileName</code> if it is a
     *         class-file that is no anonymous {@link Class}, else <code>null</code>.
     */
    public static String fixClassName(String fileName)
    {
        // replace path separator by package separator
        String result = fileName.replace('/', '.');

        // handle inner classes...
        final int lastDollar = result.lastIndexOf('$');
        if (lastDollar > 0)
        {
            char innerChar = result.charAt(lastDollar + 1);
            // ignore anonymous inner class
            if ((innerChar >= '0') && (innerChar <= '9'))
                return null;

            // TODO: check we really don't need to replace '$' by '.'
            // return result.replace('$', '.');
        }

        return result;
    }

    /**
     * Find the file (.jar or .class usually) that host this class.
     * 
     * @param fullClassName
     *        The class name to look for.
     * @return The File that contains this class.
     *         It will return <code>null</code> if the class was not loaded from a file or for any
     *         other error.
     */
    public static File getFile(String fullClassName)
    {
        final String className = ClassUtil.getBaseClassName(fullClassName);

        try
        {
            final Class<?> clazz = findClass(className);

            URL classUrl = clazz.getResource(clazz.getSimpleName() + ".class");
            if (classUrl == null)
                classUrl = clazz.getResource(clazz.getName() + ".class");

            final URLConnection connection = classUrl.openConnection();

            if (connection instanceof JarURLConnection)
                return new File(((JarURLConnection) connection).getJarFileURL().toURI());
            if (connection instanceof FileURLConnection)
                return new File(classUrl.toURI());
        }
        catch (Exception e)
        {
            // ignore
            IcyExceptionHandler.showErrorMessage(e, false, true);
        }

        return null;
    }

    /**
     * @deprecated Use {@link ReflectionUtil#getMethod(Object, String, boolean, Class...)} instead
     */
    @Deprecated
    public static Method getMethod(Object object, String methodName, boolean forceAccess, Class<?>... parameterTypes)
            throws SecurityException, NoSuchMethodException
    {
        return ReflectionUtil.getMethod(object, methodName, forceAccess, parameterTypes);
    }

    /**
     * @deprecated Use {@link ReflectionUtil#invokeMethod(Object, String, boolean, Object...)} instead
     */
    @Deprecated
    public static Object invokeMethod(Object object, String methodName, boolean forceAccess, Object... args)
            throws SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException,
            InvocationTargetException
    {
        return ReflectionUtil.invokeMethod(object, methodName, forceAccess, args);
    }

    /**
     * @deprecated Use {@link ReflectionUtil#getField(Object, String, boolean)} instead
     */
    @Deprecated
    public static Field getField(Object object, String fieldName, boolean forceAccess) throws SecurityException,
            NoSuchFieldException
    {
        return ReflectionUtil.getField(object, fieldName, forceAccess);
    }

    /**
     * @deprecated Use {@link ReflectionUtil#getFieldObject(Object, String, boolean)} instead
     */
    @Deprecated
    public static Object getFieldObject(Object object, String fieldName, boolean forceAccess)
            throws IllegalArgumentException, IllegalAccessException, SecurityException, NoSuchFieldException
    {
        return ReflectionUtil.getFieldObject(object, fieldName, forceAccess);
    }
}