001/*
002 * Copyright 2010-2015 Institut Pasteur.
003 * 
004 * This file is part of Icy.
005 * 
006 * Icy is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 * 
011 * Icy is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014 * GNU General Public License for more details.
015 * 
016 * You should have received a copy of the GNU General Public License
017 * along with Icy. If not, see <http://www.gnu.org/licenses/>.
018 */
019package icy.util;
020
021import icy.file.FileUtil;
022import icy.network.URLUtil;
023import icy.plugin.PluginLoader;
024import icy.system.IcyExceptionHandler;
025import icy.system.SystemUtil;
026
027import java.io.File;
028import java.io.IOException;
029import java.lang.reflect.Field;
030import java.lang.reflect.InvocationTargetException;
031import java.lang.reflect.Method;
032import java.lang.reflect.Modifier;
033import java.net.JarURLConnection;
034import java.net.URL;
035import java.net.URLConnection;
036import java.net.URLDecoder;
037import java.util.ArrayList;
038import java.util.Enumeration;
039import java.util.HashSet;
040import java.util.List;
041import java.util.Set;
042import java.util.Vector;
043import java.util.jar.JarEntry;
044import java.util.jar.JarFile;
045
046/**
047 * @author stephane
048 */
049public class ClassUtil
050{
051    /**
052     * Return the current thread context class loader
053     */
054    public static ClassLoader getContextClassLoader()
055    {
056        return SystemUtil.getContextClassLoader();
057    }
058
059    /**
060     * Return the system class loader
061     */
062    public static ClassLoader getSystemClassLoader()
063    {
064        return SystemUtil.getSystemClassLoader();
065    }
066
067    /**
068     * Return the list of all loaded classes by the specified {@link ClassLoader}.<br>
069     * Warning: this function is not safe and would not always work as expected.<br>
070     * It can return <code>null</code> if an error occurred.
071     */
072    public static List<Class<?>> getLoadedClasses(ClassLoader cl)
073    {
074        try
075        {
076            final Vector classes = (Vector) ReflectionUtil.getFieldObject(cl, "classes", true);
077            return new ArrayList<Class<?>>(classes);
078        }
079        catch (Exception e)
080        {
081            IcyExceptionHandler.showErrorMessage(e, false, true);
082        }
083
084        return null;
085    }
086
087    /**
088     * @param primitiveName
089     * @return The Java primitive type represented by the given name, or null if the given name does
090     *         not represent a primitive type
091     */
092    public static Class<?> getPrimitiveType(String primitiveName)
093    {
094        if (primitiveName.equals("byte"))
095            return byte.class;
096        if (primitiveName.equals("short"))
097            return short.class;
098        if (primitiveName.equals("int"))
099            return int.class;
100        if (primitiveName.equals("long"))
101            return long.class;
102        if (primitiveName.equals("char"))
103            return char.class;
104        if (primitiveName.equals("float"))
105            return float.class;
106        if (primitiveName.equals("double"))
107            return double.class;
108        if (primitiveName.equals("boolean"))
109            return boolean.class;
110        if (primitiveName.equals("void"))
111            return void.class;
112
113        return null;
114    }
115
116    /**
117     * Get Class object of specified class name.<br>
118     * First search in Plugin Class loader then from the system class loader.<br>
119     * Primitive type are accepted.
120     * 
121     * @throws ClassNotFoundException
122     */
123    public static Class<?> findClass(String className) throws ClassNotFoundException
124    {
125        try
126        {
127            // first try to load from Plugin class loader
128            return PluginLoader.loadClass(className);
129        }
130        catch (ClassNotFoundException e1)
131        {
132            try
133            {
134                // then try to load from System class loader
135                return ClassLoader.getSystemClassLoader().loadClass(className);
136            }
137            catch (ClassNotFoundException e2)
138            {
139                try
140                {
141                    // try forName style from Plugin class loader with initialization
142                    return Class.forName(className, true, PluginLoader.getLoader());
143                }
144                catch (ClassNotFoundException e3)
145                {
146                    try
147                    {
148                        // try forName style from Plugin class loader without initialization
149                        return Class.forName(className, false, PluginLoader.getLoader());
150                    }
151                    catch (ClassNotFoundException e4)
152                    {
153                        // try with primitive type...
154                        final Class<?> result = getPrimitiveType(className);
155
156                        if (result != null)
157                            return result;
158
159                        // last luck...
160                        return Class.forName(className);
161                    }
162                }
163            }
164        }
165    }
166
167    /**
168     * Transform the specified path in qualified name.<br>
169     * <br>
170     * ex : "document/class/loader.class" --> "document.class.loader.class" (unix)
171     * "document\class\loader.class" --> "document.class.loader.class" (win)
172     * 
173     * @param path
174     */
175    public static String getQualifiedNameFromPath(String path)
176    {
177        return FileUtil.getGenericPath(path).replace(FileUtil.separatorChar, '.');
178    }
179
180    /**
181     * Transform the specified qualified name in path.<br>
182     * Be careful, this function do not handle the file extension.<br>
183     * <br>
184     * ex : "plugins.user.loader.test" --> "plugins/user/loader/test"
185     */
186    public static String getPathFromQualifiedName(String qualifiedName)
187    {
188        return qualifiedName.replace('.', FileUtil.separatorChar);
189    }
190
191    /**
192     * Get package name<br>
193     * ex : "plugin.test.myClass" --> "plugin.test"
194     */
195    public static String getPackageName(String className)
196    {
197        final int index = className.lastIndexOf('.');
198
199        if (index != -1)
200            return className.substring(0, index);
201
202        return "";
203    }
204
205    /**
206     * Get first package name<br>
207     * ex : "plugin.test.myClass" --> "plugin"
208     */
209    public static String getFirstPackageName(String className)
210    {
211        final String packageName = getPackageName(className);
212        final int index = packageName.lastIndexOf('.');
213
214        if (index != -1)
215            return packageName.substring(0, index);
216
217        return packageName;
218    }
219
220    /**
221     * Get the base class name<br>
222     * ex : "plugin.myClass$InternClass$1" --> "plugin.myClass"
223     */
224    public static String getBaseClassName(String className)
225    {
226        // handle inner classes...
227        final int lastDollar = className.indexOf('$');
228        if (lastDollar > 0)
229            return className.substring(0, lastDollar);
230
231        return className;
232    }
233
234    /**
235     * Get simple class name<br>
236     * ex : "plugin.test.myClass$InternClass$1" --> "myClass$InternClass$1"
237     */
238    public static String getSimpleClassName(String className)
239    {
240        final int index = className.lastIndexOf('.');
241
242        if (index != -1)
243            return className.substring(index + 1);
244
245        return className;
246    }
247
248    /**
249     * Returns the source JAR file (if any) from where the specified class has been loaded from
250     */
251    public static String getJarPath(Class<?> c)
252    {
253        final URL url = c.getResource('/' + c.getName().replace('.', '/') + ".class");
254
255        // JAR url ?
256        if ((url != null) && url.getProtocol().equalsIgnoreCase("jar"))
257        {
258            String result;
259            int ind;
260            
261            // extract 
262            result = url.getPath();
263            
264            ind = result.indexOf(':');
265            if (ind != -1) result= result.substring(ind+1);
266            
267            ind = result.indexOf('!');
268            if (ind != -1) result = result.substring(0, ind);
269            
270            return new File(result).getAbsolutePath();
271        }
272
273        return "";
274    }
275
276    /**
277     * Return true if clazz implements the specified interface
278     */
279    public static Class<?>[] getInterfaces(Class<?> c)
280    {
281        if (c == null)
282            return new Class[0];
283
284        return c.getInterfaces();
285    }
286
287    /**
288     * Return true if class is abstract
289     */
290    public static boolean isAbstract(Class<?> c)
291    {
292        if (c == null)
293            return false;
294
295        return Modifier.isAbstract(c.getModifiers());
296    }
297
298    /**
299     * Return true if class is public
300     */
301    public static boolean isPublic(Class<?> c)
302    {
303        if (c == null)
304            return false;
305
306        return Modifier.isPublic(c.getModifiers());
307    }
308
309    /**
310     * Return true if class is private
311     */
312    public static boolean isPrivate(Class<?> c)
313    {
314        if (c == null)
315            return false;
316
317        return Modifier.isPrivate(c.getModifiers());
318    }
319
320    /**
321     * Return true if clazz is the same class or extends baseClass
322     */
323    public static boolean isSubClass(Class<?> clazz, Class<?> baseClass)
324    {
325        if ((clazz == null) || (baseClass == null))
326            return false;
327
328        return baseClass.isAssignableFrom(clazz);
329    }
330
331    /**
332     * This method returns all resources that are located in the package identified by the given
333     * <code>packageName</code>.<br>
334     * <b>WARNING:</b><br>
335     * This is a relative expensive operation. Depending on your classpath multiple directories, JAR and WAR files may
336     * need to be scanned.<br>
337     * Original code written by Jorg Hohwiller for the m-m-m project (http://m-m-m.sf.net)
338     * 
339     * @param packageName
340     *        is the name of the {@link Package} to scan (ex: "java.awt.metrics")
341     * @param extension
342     *        resource extension if we want to retrieve only a specific type of resource (ex: ".class")<br>
343     *        Note that extension filtering is not case sensitive.
344     * @param recursive
345     *        if set to <code>true</code> files from sub packages/folder are also returned.
346     * @param includeFolder
347     *        if <code>true</code> folder entry are also returned
348     * @param includeJar
349     *        if <code>true</code> all sub JAR files are also scanned
350     * @param includeHidden
351     *        if <code>true</code> all hidden files (starting by '.' character) are also scanned
352     * @return
353     *         all files contained in this package represented in path format (ex: "java/awt/geom/Rectangle2D.class")
354     */
355    public static List<String> getResourcesInPackage(String packageName, String extension, boolean recursive,
356            boolean includeFolder, boolean includeJar, boolean includeHidden) throws IOException
357    {
358        final List<String> result = new ArrayList<String>();
359
360        getResourcesInPackage(packageName, extension, recursive, includeFolder, includeJar, includeHidden, result);
361
362        return result;
363    }
364
365    /**
366     * Internal use, see {@link #getResourcesInPackage(String, String, boolean)}
367     */
368    private static void getResourcesInPackage(String packageName, String extension, boolean recursive,
369            boolean includeFolder, boolean includeJar, boolean includeHidden, List<String> result) throws IOException
370    {
371        final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
372        final String path = ClassUtil.getPathFromQualifiedName(packageName);
373        final Enumeration<URL> urls = classLoader.getResources(path);
374        final String ext = StringUtil.isEmpty(extension) ? "" : extension.toLowerCase();
375
376        while (urls.hasMoreElements())
377        {
378            final URL packageUrl = urls.nextElement();
379            final String urlPath = URLDecoder.decode(packageUrl.getFile(), "UTF-8");
380            final String protocol = packageUrl.getProtocol().toLowerCase();
381
382            if ("file".equals(protocol))
383                getResourcesInPath(urlPath, packageName, recursive, includeFolder, includeJar, includeHidden, result);
384            else if ("jar".equals(protocol))
385            {
386                final JarURLConnection connection = (JarURLConnection) packageUrl.openConnection();
387                final JarFile jarFile = connection.getJarFile();
388                final Enumeration<JarEntry> jarEntryEnumeration = jarFile.entries();
389                final String pathWithPrefix = path + '/';
390                final int prefixLength = path.length() + 1;
391
392                while (jarEntryEnumeration.hasMoreElements())
393                {
394                    final JarEntry jarEntry = jarEntryEnumeration.nextElement();
395                    String absoluteFileName = jarEntry.getName();
396
397                    if (StringUtil.isEmpty(extension) || absoluteFileName.endsWith(ext))
398                    {
399                        if (absoluteFileName.startsWith("/"))
400                            absoluteFileName = absoluteFileName.substring(1);
401
402                        boolean accept = true;
403                        if (absoluteFileName.startsWith(pathWithPrefix))
404                        {
405                            if (!recursive)
406                            {
407                                int index = absoluteFileName.indexOf('/', prefixLength);
408                                if (index != -1)
409                                    accept = false;
410                            }
411
412                            if (!includeFolder && jarEntry.isDirectory())
413                                accept = false;
414
415                            if (accept)
416                                result.add(absoluteFileName);
417                        }
418                    }
419                }
420
421                jarFile.close();
422            }
423        }
424    }
425
426    /**
427     * This method returns all resources that are located in the specified path.<br>
428     * 
429     * @param path
430     *        path to scan.
431     * @param recursive
432     *        if <code>true</code> all sub folder are also scanned.
433     * @param includeJar
434     *        if <code>true</code> all JAR files are also scanned
435     * @param includeHidden
436     *        if <code>true</code> all hidden files (starting by '.' character) are also scanned
437     * @return list of found resources.
438     */
439    public static List<String> getResourcesInPath(String path, boolean recursive, boolean includeFolder,
440            boolean includeJar, boolean includeHidden)
441    {
442        final List<String> result = new ArrayList<String>();
443
444        getResourcesInPath(path, ClassUtil.getQualifiedNameFromPath(path), recursive, includeFolder, includeJar,
445                includeHidden, result);
446
447        return result;
448    }
449
450    /**
451     * This method returns all resources that are located in the specified path.<br>
452     * 
453     * @param path
454     *        path to scan.
455     * @param basePath
456     *        path prefix
457     * @param recursive
458     *        if <code>true</code> all sub folder are also scanned.
459     * @param includeJar
460     *        if <code>true</code> all JAR files are also scanned
461     * @param includeHidden
462     *        if <code>true</code> all hidden files (starting by '.' character) are also scanned
463     * @return set of found class.
464     */
465    public static List<String> getResourcesInPath(String path, String basePath, boolean recursive,
466            boolean includeFolder, boolean includeJar, boolean includeHidden)
467    {
468        final List<String> result = new ArrayList<String>();
469
470        getResourcesInPath(path, basePath, recursive, includeFolder, includeJar, includeHidden, result);
471
472        return result;
473    }
474
475    /**
476     * This method returns all resources that are located in the specified path.<br>
477     * 
478     * @param path
479     *        path to scan.
480     * @param basePath
481     *        path prefix
482     * @param recursive
483     *        if <code>true</code> all sub folder are also scanned.
484     * @param includeJar
485     *        if <code>true</code> all JAR files are also scanned
486     * @param includeHidden
487     *        if <code>true</code> all hidden files (starting by '.' character) are also scanned
488     * @param result
489     *        result list
490     */
491    public static void getResourcesInPath(String path, String basePath, boolean recursive, boolean includeFolder,
492            boolean includeJar, boolean includeHidden, List<String> result)
493    {
494        final File file = new File(path);
495        final String qualifiedPath;
496
497        if (StringUtil.isEmpty(basePath))
498            qualifiedPath = "";
499        else
500            qualifiedPath = basePath + "/";
501
502        if (file.isDirectory())
503        {
504            if (recursive)
505                findResourcesRecursive(file, includeFolder, includeJar, includeHidden, result, qualifiedPath);
506            else
507            {
508                for (File subFile : file.listFiles())
509                    findResourceInFile(subFile, includeJar, includeHidden, result, qualifiedPath);
510            }
511        }
512        else
513            findResourceInFile(file, includeJar, includeHidden, result, qualifiedPath);
514    }
515
516    private static void findResourcesRecursive(File directory, boolean includeFolder, boolean includeJar,
517            boolean includeHidden, List<String> result, String basePath)
518    {
519        for (File childFile : directory.listFiles())
520        {
521            final String childFilename = childFile.getName();
522
523            // folder ?
524            if (childFile.isDirectory())
525            {
526                if (!includeHidden && childFilename.startsWith("."))
527                    continue;
528
529                // include this folder entry
530                if (includeFolder)
531                    result.add(basePath + childFilename);
532
533                // then search in sub folder
534                findResourcesRecursive(childFile, includeJar, includeFolder, includeHidden, result,
535                        basePath + childFilename + '/');
536            }
537            else
538                findResourceInFile(childFile, includeJar, includeHidden, result, basePath);
539        }
540    }
541
542    /**
543     * Search for all classes in specified file
544     */
545    public static void findResourceInFile(File file, boolean includeJar, boolean includeHidden, List<String> result,
546            String basePath)
547    {
548        final String shortName = file.getName();
549
550        if (!includeHidden && shortName.startsWith("."))
551            return;
552
553        final String fileName = file.getPath();
554
555        if (FileUtil.getFileExtension(fileName, false).toLowerCase().equals("jar"))
556        {
557            if (includeJar)
558                JarUtil.getAllFiles(fileName, false, includeHidden, result);
559        }
560        else
561            result.add(basePath + shortName);
562    }
563
564    /**
565     * This method finds all classes that are located in the package identified by the given
566     * <code>packageName</code>.<br>
567     * <b>ATTENTION:</b><br>
568     * This is a relative expensive operation. Depending on your classpath multiple
569     * directories,JAR-, and WAR-files may need to be scanned. <br>
570     * 
571     * @param packageName
572     *        is the name of the {@link Package} to scan.
573     * @param includeSubPackages
574     *        - if <code>true</code> all sub-packages of the specified {@link Package} will be
575     *        included in the search.
576     * @return found classes set
577     * @throws IOException
578     *         if the operation failed with an I/O error.
579     */
580    public static Set<String> findClassNamesInPackage(String packageName, boolean includeSubPackages) throws IOException
581    {
582        final HashSet<String> classes = new HashSet<String>();
583
584        findClassNamesInPackage(packageName, includeSubPackages, classes);
585
586        return classes;
587    }
588
589    /**
590     * This method finds all classes that are located in the package identified by the given
591     * <code>packageName</code>.<br>
592     * <b>ATTENTION:</b><br>
593     * This is a relative expensive operation. Depending on your classpath multiple
594     * directories,JAR-, and WAR-files may need to be scanned. <br>
595     * Original code written by Jorg Hohwiller for the m-m-m project (http://m-m-m.sf.net)
596     * 
597     * @param packageName
598     *        is the name of the {@link Package} to scan.
599     * @param includeSubPackages
600     *        - if <code>true</code> all sub-packages of the specified {@link Package} will be
601     *        included in the search.
602     * @param classes
603     *        save found classes here
604     */
605    public static void findClassNamesInPackage(String packageName, boolean includeSubPackages, Set<String> classes)
606            throws IOException
607    {
608        final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
609        final String path = ClassUtil.getPathFromQualifiedName(packageName);
610        final Enumeration<URL> urls = classLoader.getResources(path);
611
612        while (urls.hasMoreElements())
613        {
614            final URL packageUrl = urls.nextElement();
615            final String urlPath = URLDecoder.decode(packageUrl.getFile(), "UTF-8");
616            final String protocol = packageUrl.getProtocol().toLowerCase();
617
618            if ("file".equals(protocol))
619                findClassNamesInPath(urlPath, packageName, includeSubPackages, classes);
620            else if ("jar".equals(protocol))
621            {
622                final JarURLConnection connection = (JarURLConnection) packageUrl.openConnection();
623                final JarFile jarFile = connection.getJarFile();
624                final Enumeration<JarEntry> jarEntryEnumeration = jarFile.entries();
625                final String pathWithPrefix = path + '/';
626                final int prefixLength = path.length() + 1;
627
628                while (jarEntryEnumeration.hasMoreElements())
629                {
630                    final JarEntry jarEntry = jarEntryEnumeration.nextElement();
631                    String absoluteFileName = jarEntry.getName();
632
633                    if (absoluteFileName.endsWith(".class"))
634                    {
635                        if (absoluteFileName.startsWith("/"))
636                            absoluteFileName = absoluteFileName.substring(1);
637
638                        boolean accept = true;
639                        if (absoluteFileName.startsWith(pathWithPrefix))
640                        {
641                            String qualifiedName = absoluteFileName.replace('/', '.');
642
643                            if (!includeSubPackages)
644                            {
645                                int index = absoluteFileName.indexOf('/', prefixLength);
646                                if (index != -1)
647                                    accept = false;
648                            }
649
650                            if (accept)
651                            {
652                                final String className = filenameToClassname(qualifiedName);
653                                if (className != null)
654                                    classes.add(className);
655                            }
656                        }
657                    }
658                }
659
660                jarFile.close();
661            }
662        }
663    }
664
665    /**
666     * This method finds all classes that are located in the specified directory.<br>
667     * 
668     * @param path
669     *        path to scan.
670     * @param includeSubDir
671     *        if <code>true</code> all sub-directory are also scanned.
672     * @return set of found class.
673     */
674    public static HashSet<String> findClassNamesInPath(String path, boolean includeSubDir)
675    {
676        return findClassNamesInPath(path, ClassUtil.getQualifiedNameFromPath(path), includeSubDir, true);
677    }
678
679    /**
680     * This method finds all classes that are located in the specified directory.<br>
681     * 
682     * @param path
683     *        path to scan.
684     * @param includeSubDir
685     *        if <code>true</code> all sub-directory are also scanned.
686     * @param includeJar
687     *        if <code>true</code> all JAR files are also scanned
688     * @return set of found class.
689     */
690    public static HashSet<String> findClassNamesInPath(String path, boolean includeSubDir, boolean includeJar)
691    {
692        return findClassNamesInPath(path, ClassUtil.getQualifiedNameFromPath(path), includeSubDir, includeJar);
693    }
694
695    /**
696     * This method finds all classes that are located in the specified directory.<br>
697     * 
698     * @param path
699     *        path to scan.
700     * @param packageName
701     *        package name prefix
702     * @param includeSubDir
703     *        if <code>true</code> all sub-directory are also scanned.
704     * @return set of found class.
705     */
706    public static HashSet<String> findClassNamesInPath(String path, String packageName, boolean includeSubDir)
707    {
708        final HashSet<String> classes = new HashSet<String>();
709
710        findClassNamesInPath(path, packageName, includeSubDir, true, classes);
711
712        return classes;
713    }
714
715    /**
716     * This method finds all classes that are located in the specified directory.<br>
717     * 
718     * @param path
719     *        path to scan.
720     * @param packageName
721     *        package name prefix
722     * @param includeSubDir
723     *        if <code>true</code> all sub-directory are also scanned.
724     * @param includeJar
725     *        if <code>true</code> all JAR files are also scanned
726     * @return set of found class.
727     */
728    public static HashSet<String> findClassNamesInPath(String path, String packageName, boolean includeSubDir,
729            boolean includeJar)
730    {
731        final HashSet<String> classes = new HashSet<String>();
732
733        findClassNamesInPath(path, packageName, includeSubDir, includeJar, classes);
734
735        return classes;
736    }
737
738    /**
739     * This method finds all classes that are located in the specified directory.<br>
740     * 
741     * @param path
742     *        path to scan.
743     * @param packageName
744     *        package name prefix
745     * @param includeSubDir
746     *        if <code>true</code> all sub-directory are also scanned.
747     * @param classes
748     *        save found classes here
749     */
750    public static void findClassNamesInPath(String path, String packageName, boolean includeSubDir, Set<String> classes)
751    {
752        findClassNamesInPath(path, packageName, includeSubDir, true, classes);
753    }
754
755    /**
756     * This method finds all classes that are located in the specified directory.<br>
757     * 
758     * @param path
759     *        path to scan.
760     * @param packageName
761     *        package name prefix
762     * @param includeSubDir
763     *        if <code>true</code> all sub-directory are also scanned.
764     * @param includeJar
765     *        if <code>true</code> all JAR files are also scanned
766     * @param classes
767     *        save found classes here
768     */
769    public static void findClassNamesInPath(String path, String packageName, boolean includeSubDir, boolean includeJar,
770            Set<String> classes)
771    {
772        final File dir = new File(path);
773        final String qualifiedName;
774
775        if (StringUtil.isEmpty(packageName))
776            qualifiedName = "";
777        else
778            qualifiedName = packageName + '.';
779
780        if (dir.isDirectory())
781        {
782            if (includeSubDir)
783                findClassNamesRecursive(dir, includeJar, classes, qualifiedName);
784            else
785                for (File file : dir.listFiles())
786                    findClassNameInFile(file, includeJar, classes, qualifiedName);
787        }
788        else
789            findClassNameInFile(dir, classes, qualifiedName);
790    }
791
792    private static void findClassNamesRecursive(File directory, boolean includeJar, Set<String> classSet,
793            String qualifiedName)
794    {
795        for (File childFile : directory.listFiles())
796        {
797            final String childFilename = childFile.getName();
798
799            // files or directories starting with "." aren't allowed
800            if (!childFilename.startsWith("."))
801            {
802                if (childFile.isDirectory())
803                    findClassNamesRecursive(childFile, includeJar, classSet, qualifiedName + childFilename + '.');
804                else
805                    findClassNameInFile(childFile, includeJar, classSet, qualifiedName);
806            }
807        }
808    }
809
810    /**
811     * Search for all classes in specified file
812     */
813    public static void findClassNameInFile(File file, boolean includeJar, Set<String> classSet,
814            String qualifiedNamePrefix)
815    {
816        final String fileName = file.getPath();
817        if (FileUtil.getFileExtension(fileName, false).toLowerCase().equals("jar"))
818        {
819            if (includeJar)
820                findClassNamesInJAR(fileName, classSet);
821        }
822        else
823            addClassFileName(file.getName(), classSet, qualifiedNamePrefix);
824    }
825
826    /**
827     * Search for all classes in specified file
828     */
829    public static void findClassNameInFile(File file, Set<String> classSet, String qualifiedNamePrefix)
830    {
831        findClassNameInFile(file, true, classSet, qualifiedNamePrefix);
832    }
833
834    /**
835     * Search for all classes in JAR file
836     */
837    public static void findClassNamesInJAR(String fileName, Set<String> classSet)
838    {
839        final JarFile jarFile;
840
841        try
842        {
843            jarFile = new JarFile(fileName);
844        }
845        catch (IOException e)
846        {
847            System.err.println("Cannot open " + fileName + ":");
848            IcyExceptionHandler.showErrorMessage(e, false, true);
849            return;
850        }
851
852        final Enumeration<JarEntry> entries = jarFile.entries();
853
854        while (entries.hasMoreElements())
855        {
856            final JarEntry jarEntry = entries.nextElement();
857
858            if (!jarEntry.isDirectory())
859                addClassFileName(jarEntry.getName(), classSet, "");
860        }
861
862        try
863        {
864            jarFile.close();
865        }
866        catch (IOException e)
867        {
868            // ignore
869        }
870    }
871
872    /**
873     * Search for all classes in JAR file
874     * 
875     * @param fileName
876     */
877    public static Set<String> findClassNamesInJAR(String fileName)
878    {
879        final HashSet<String> result = new HashSet<String>();
880
881        findClassNamesInJAR(fileName, result);
882
883        return result;
884    }
885
886    private static void addClassFileName(String fileName, Set<String> classSet, String prefix)
887    {
888        final String simpleClassName = filenameToClassname(fileName);
889
890        if (simpleClassName != null)
891            classSet.add(prefix + simpleClassName);
892    }
893
894    /**
895     * This method checks and transforms the filename of a potential {@link Class} given by <code>fileName</code>.
896     * 
897     * @param fileName
898     *        is the filename.
899     * @return the according Java {@link Class#getName() class-name} for the given <code>fileName</code> if it is a
900     *         class-file that is no anonymous {@link Class}, else <code>null</code>.
901     */
902    public static String filenameToClassname(String fileName)
903    {
904        // class file ?
905        if (fileName.toLowerCase().endsWith(".class"))
906            // remove ".class" extension and fix classname
907            return fixClassName(fileName.substring(0, fileName.length() - 6));
908
909        return null;
910    }
911
912    /**
913     * This method checks and transforms the filename of a potential {@link Class} given by <code>fileName</code>.<br>
914     * Code written by Jorg Hohwiller for the m-m-m project (http://m-m-m.sf.net)
915     * 
916     * @param fileName
917     *        is the filename.
918     * @return the according Java {@link Class#getName() class-name} for the given <code>fileName</code> if it is a
919     *         class-file that is no anonymous {@link Class}, else <code>null</code>.
920     */
921    public static String fixClassName(String fileName)
922    {
923        // replace path separator by package separator
924        String result = fileName.replace('/', '.');
925
926        // handle inner classes...
927        final int lastDollar = result.lastIndexOf('$');
928        if (lastDollar > 0)
929        {
930            char innerChar = result.charAt(lastDollar + 1);
931            // ignore anonymous inner class
932            if ((innerChar >= '0') && (innerChar <= '9'))
933                return null;
934
935            // TODO: check we really don't need to replace '$' by '.'
936            // return result.replace('$', '.');
937        }
938
939        return result;
940    }
941
942    /**
943     * Find the file (.jar or .class usually) that host this class.
944     * 
945     * @param fullClassName
946     *        The class name to look for.
947     * @return The File that contains this class.
948     *         It will return <code>null</code> if the class was not loaded from a file or for any
949     *         other error.
950     */
951    public static File getFile(String fullClassName)
952    {
953        final String className = ClassUtil.getBaseClassName(fullClassName);
954
955        try
956        {
957            final Class<?> clazz = findClass(className);
958
959            URL classUrl = clazz.getResource(clazz.getSimpleName() + ".class");
960            if (classUrl == null)
961                classUrl = clazz.getResource(clazz.getName() + ".class");
962
963            final URLConnection connection = classUrl.openConnection();
964
965            if (connection instanceof JarURLConnection)
966                return new File(((JarURLConnection) connection).getJarFileURL().toURI());
967            // if (connection instanceof FileURLConnection)
968            // return new File(classUrl.toURI());
969            // try from URI
970            return new File(classUrl.toURI());
971        }
972        catch (Exception e)
973        {
974            // ignore
975            IcyExceptionHandler.showErrorMessage(e, false, true);
976        }
977
978        return null;
979    }
980
981    /**
982     * @deprecated Use {@link ReflectionUtil#getMethod(Object, String, boolean, Class...)} instead
983     */
984    @Deprecated
985    public static Method getMethod(Object object, String methodName, boolean forceAccess, Class<?>... parameterTypes)
986            throws SecurityException, NoSuchMethodException
987    {
988        return ReflectionUtil.getMethod(object, methodName, forceAccess, parameterTypes);
989    }
990
991    /**
992     * @deprecated Use {@link ReflectionUtil#invokeMethod(Object, String, boolean, Object...)} instead
993     */
994    @Deprecated
995    public static Object invokeMethod(Object object, String methodName, boolean forceAccess, Object... args)
996            throws SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException,
997            InvocationTargetException
998    {
999        return ReflectionUtil.invokeMethod(object, methodName, forceAccess, args);
1000    }
1001
1002    /**
1003     * @deprecated Use {@link ReflectionUtil#getField(Object, String, boolean)} instead
1004     */
1005    @Deprecated
1006    public static Field getField(Object object, String fieldName, boolean forceAccess)
1007            throws SecurityException, NoSuchFieldException
1008    {
1009        return ReflectionUtil.getField(object, fieldName, forceAccess);
1010    }
1011
1012    /**
1013     * @deprecated Use {@link ReflectionUtil#getFieldObject(Object, String, boolean)} instead
1014     */
1015    @Deprecated
1016    public static Object getFieldObject(Object object, String fieldName, boolean forceAccess)
1017            throws IllegalArgumentException, IllegalAccessException, SecurityException, NoSuchFieldException
1018    {
1019        return ReflectionUtil.getFieldObject(object, fieldName, forceAccess);
1020    }
1021}