001/**
002 * Copyright (c) 2011, The University of Southampton and the individual contributors.
003 * All rights reserved.
004 *
005 * Redistribution and use in source and binary forms, with or without modification,
006 * are permitted provided that the following conditions are met:
007 *
008 *   *  Redistributions of source code must retain the above copyright notice,
009 *      this list of conditions and the following disclaimer.
010 *
011 *   *  Redistributions in binary form must reproduce the above copyright notice,
012 *      this list of conditions and the following disclaimer in the documentation
013 *      and/or other materials provided with the distribution.
014 *
015 *   *  Neither the name of the University of Southampton nor the names of its
016 *      contributors may be used to endorse or promote products derived from this
017 *      software without specific prior written permission.
018 *
019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029 */
030package org.openimaj.util.reflection;
031
032import java.io.File;
033import java.io.IOException;
034import java.net.URL;
035import java.util.ArrayList;
036import java.util.Enumeration;
037import java.util.List;
038import java.util.jar.JarEntry;
039import java.util.jar.JarFile;
040
041/**
042 * Utility methods for finding classes.
043 * 
044 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
045 */
046public class ClassFinder {
047        /**
048         * Scans all classes accessible from the context class loader which belong
049         * to the given package and sub-packages.
050         * 
051         * @param pkg
052         *            The base package
053         * @return The classes
054         * @throws IOException
055         */
056        public static List<Class<?>> findClasses(Package pkg) throws IOException {
057                return findClasses(pkg.getName());
058        }
059
060        /**
061         * Scans all classes accessible from the context class loader which belong
062         * to the given package and subpackages.
063         * 
064         * @param packageName
065         *            The base package
066         * @return The classes
067         * @throws IOException
068         */
069        public static List<Class<?>> findClasses(String packageName) throws IOException {
070                final List<Class<?>> classes = new ArrayList<Class<?>>();
071
072                final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
073
074                final String path = packageName.replace('.', '/');
075                final Enumeration<URL> resources = classLoader.getResources(path);
076
077                while (resources.hasMoreElements()) {
078                        final URL resource = resources.nextElement();
079
080                        if (resource.getProtocol().equals("file")) {
081                                classes.addAll(findClassesInDir(new File(resource.getFile()), packageName));
082                        } else if (resource.getProtocol().equals("jar")) {
083                                final String rf = resource.getFile();
084                                final File file = new File(rf.substring(5, rf.indexOf("!")));
085                                classes.addAll(findClassesInJar(file, packageName));
086                        }
087                }
088                return classes;
089        }
090
091        /**
092         * Recursive method to find all classes in a given directory and subdirs.
093         * 
094         * @param directory
095         *            The base directory
096         * @param packageName
097         *            The package name for classes found inside the base directory
098         * @return The classes
099         */
100        public static List<Class<?>> findClassesInDir(File directory, String packageName) {
101                final List<Class<?>> classes = new ArrayList<Class<?>>();
102                if (!directory.exists()) {
103                        return classes;
104                }
105                final File[] files = directory.listFiles();
106                for (final File file : files) {
107                        if (file.isDirectory()) {
108                                classes.addAll(findClassesInDir(file, packageName + "." + file.getName()));
109                        } else if (file.getName().endsWith(".class")) {
110                                try {
111                                        classes.add(Class.forName(packageName + '.'
112                                                        + file.getName().substring(0, file.getName().length() - 6)));
113                                } catch (final ClassNotFoundException e) {
114                                        // do nothing
115                                }
116                        }
117                }
118                return classes;
119        }
120
121        /**
122         * Finds all the classes in a given package or its subpackages within a jar
123         * file.
124         * 
125         * @param jarFile
126         *            The jar file
127         * @param packageName
128         *            The package name
129         * @return The classes
130         * @throws IOException
131         */
132        public static List<Class<?>> findClassesInJar(File jarFile, String packageName) throws IOException {
133                final List<Class<?>> classes = new ArrayList<Class<?>>();
134
135                JarFile jar = null;
136                try {
137                        jar = new JarFile(jarFile);
138                        final Enumeration<JarEntry> enu = jar.entries();
139
140                        final String path = packageName.replace(".", "/");
141
142                        while (enu.hasMoreElements()) {
143                                final JarEntry je = enu.nextElement();
144                                final String name = je.getName();
145
146                                if (name.startsWith(path) && name.endsWith(".class")) {
147                                        try {
148                                                classes.add(Class.forName(name.replace("/", ".").substring(0, name.length() - 6)));
149                                        } catch (final ClassNotFoundException e) {
150                                                // do nothing
151                                        }
152                                }
153                        }
154                } finally {
155                        if (jar != null) {
156                                try {
157                                        jar.close();
158                                } catch (final IOException e) {
159                                }
160                        }
161                }
162
163                return classes;
164        }
165}